From 4ca84ac1f3ac5aa28cecdd31c54c992ae2e1ac46 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Mon, 4 May 2026 16:42:49 -0500 Subject: [PATCH 1/2] fix(shopinbit): defend against API contract drift --- lib/models/isar/models/shopinbit_ticket.dart | 5 + .../isar/models/shopinbit_ticket.g.dart | 221 +++++++++++++++++- .../shopinbit/shopinbit_order_model.dart | 23 +- .../shopinbit/shopinbit_ticket_detail.dart | 6 +- .../shopinbit/shopinbit_tickets_view.dart | 4 +- lib/services/shopinbit/src/models/ticket.dart | 28 ++- .../shopinbit/src/models/webhook_event.dart | 18 +- 7 files changed, 290 insertions(+), 15 deletions(-) diff --git a/lib/models/isar/models/shopinbit_ticket.dart b/lib/models/isar/models/shopinbit_ticket.dart index 0a2ac53d7..4ccf62928 100644 --- a/lib/models/isar/models/shopinbit_ticket.dart +++ b/lib/models/isar/models/shopinbit_ticket.dart @@ -16,6 +16,11 @@ class ShopInBitTicket { late ShopInBitCategory category; @enumerated late ShopInBitOrderStatus status; + // Raw API state string (e.g. "OFFER AVAILABLE") preserved alongside the + // mapped enum. If ShopinBit renames a state or adds a new one, the enum + // will read as `pending` but `statusRaw` retains the canonical string so a + // future client update can re-derive the correct status via migration. + String? statusRaw; late String requestDescription; late String deliveryCountry; late String? offerProductName; diff --git a/lib/models/isar/models/shopinbit_ticket.g.dart b/lib/models/isar/models/shopinbit_ticket.g.dart index ecd600a15..0385ab290 100644 --- a/lib/models/isar/models/shopinbit_ticket.g.dart +++ b/lib/models/isar/models/shopinbit_ticket.g.dart @@ -131,8 +131,13 @@ const ShopInBitTicketSchema = CollectionSchema( type: IsarType.byte, enumMap: _ShopInBitTicketstatusEnumValueMap, ), - r'ticketId': PropertySchema( + r'statusRaw': PropertySchema( id: 22, + name: r'statusRaw', + type: IsarType.string, + ), + r'ticketId': PropertySchema( + id: 23, name: r'ticketId', type: IsarType.string, ), @@ -229,6 +234,12 @@ int _shopInBitTicketEstimateSize( bytesCount += 3 + object.shippingName.length * 3; bytesCount += 3 + object.shippingPostalCode.length * 3; bytesCount += 3 + object.shippingStreet.length * 3; + { + final value = object.statusRaw; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } bytesCount += 3 + object.ticketId.length * 3; return bytesCount; } @@ -266,7 +277,8 @@ void _shopInBitTicketSerialize( writer.writeString(offsets[19], object.shippingPostalCode); writer.writeString(offsets[20], object.shippingStreet); writer.writeByte(offsets[21], object.status.index); - writer.writeString(offsets[22], object.ticketId); + writer.writeString(offsets[22], object.statusRaw); + writer.writeString(offsets[23], object.ticketId); } ShopInBitTicket _shopInBitTicketDeserialize( @@ -310,7 +322,8 @@ ShopInBitTicket _shopInBitTicketDeserialize( object.status = _ShopInBitTicketstatusValueEnumMap[reader.readByteOrNull(offsets[21])] ?? ShopInBitOrderStatus.pending; - object.ticketId = reader.readString(offsets[22]); + object.statusRaw = reader.readStringOrNull(offsets[22]); + object.ticketId = reader.readString(offsets[23]); return object; } @@ -381,6 +394,8 @@ P _shopInBitTicketDeserializeProp

( ShopInBitOrderStatus.pending) as P; case 22: + return (reader.readStringOrNull(offset)) as P; + case 23: return (reader.readString(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -3145,6 +3160,165 @@ extension ShopInBitTicketQueryFilter }); } + QueryBuilder + statusRawIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'statusRaw'), + ); + }); + } + + QueryBuilder + statusRawIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'statusRaw'), + ); + }); + } + + QueryBuilder + statusRawEqualTo(String? value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'statusRaw', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + statusRawGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'statusRaw', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + statusRawLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'statusRaw', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + statusRawBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'statusRaw', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + statusRawStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'statusRaw', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + statusRawEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'statusRaw', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + statusRawContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'statusRaw', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + statusRawMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'statusRaw', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + statusRawIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'statusRaw', value: ''), + ); + }); + } + + QueryBuilder + statusRawIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'statusRaw', value: ''), + ); + }); + } + QueryBuilder ticketIdEqualTo(String value, {bool caseSensitive = true}) { return QueryBuilder.apply(this, (query) { @@ -3595,6 +3769,20 @@ extension ShopInBitTicketQuerySortBy }); } + QueryBuilder + sortByStatusRaw() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'statusRaw', Sort.asc); + }); + } + + QueryBuilder + sortByStatusRawDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'statusRaw', Sort.desc); + }); + } + QueryBuilder sortByTicketId() { return QueryBuilder.apply(this, (query) { @@ -3917,6 +4105,20 @@ extension ShopInBitTicketQuerySortThenBy }); } + QueryBuilder + thenByStatusRaw() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'statusRaw', Sort.asc); + }); + } + + QueryBuilder + thenByStatusRawDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'statusRaw', Sort.desc); + }); + } + QueryBuilder thenByTicketId() { return QueryBuilder.apply(this, (query) { @@ -4110,6 +4312,13 @@ extension ShopInBitTicketQueryWhereDistinct }); } + QueryBuilder + distinctByStatusRaw({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'statusRaw', caseSensitive: caseSensitive); + }); + } + QueryBuilder distinctByTicketId({ bool caseSensitive = true, }) { @@ -4280,6 +4489,12 @@ extension ShopInBitTicketQueryProperty }); } + QueryBuilder statusRawProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'statusRaw'); + }); + } + QueryBuilder ticketIdProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'ticketId'); diff --git a/lib/models/shopinbit/shopinbit_order_model.dart b/lib/models/shopinbit/shopinbit_order_model.dart index f41aa49e3..aab436aeb 100644 --- a/lib/models/shopinbit/shopinbit_order_model.dart +++ b/lib/models/shopinbit/shopinbit_order_model.dart @@ -113,6 +113,19 @@ class ShopInBitOrderModel extends ChangeNotifier { } } + // The most recent raw API state string, persisted alongside _status so that + // we can recover from contract drift (renames / new states) without losing + // history. _status is the parsed/mapped value; _statusRaw is the source of + // truth straight from the API. + String? _statusRaw; + String? get statusRaw => _statusRaw; + set statusRaw(String? value) { + if (_statusRaw != value) { + _statusRaw = value; + notifyListeners(); + } + } + String? _offerProductName; String? get offerProductName => _offerProductName; @@ -236,6 +249,7 @@ class ShopInBitOrderModel extends ChangeNotifier { ..displayName = _displayName ..category = _category ?? ShopInBitCategory.concierge ..status = _status + ..statusRaw = _statusRaw ..requestDescription = _requestDescription ..deliveryCountry = _deliveryCountry ..offerProductName = _offerProductName @@ -271,6 +285,7 @@ class ShopInBitOrderModel extends ChangeNotifier { .._apiTicketId = ticket.apiTicketId .._ticketId = ticket.ticketId .._status = ticket.status + .._statusRaw = ticket.statusRaw .._requestDescription = ticket.requestDescription .._deliveryCountry = ticket.deliveryCountry .._offerProductName = ticket.offerProductName @@ -298,7 +313,11 @@ class ShopInBitOrderModel extends ChangeNotifier { .toList(); } - static ShopInBitOrderStatus statusFromTicketState(TicketState state) { + // Returns null when the API state cannot be mapped (TicketState.unknown). + // Callers MUST treat null as "do not overwrite the locally stored status": + // silently coercing an unknown API state to a default (e.g. pending) + // would mask contract drift and look like data regression to the user. + static ShopInBitOrderStatus? statusFromTicketState(TicketState state) { switch (state) { case TicketState.newTicket: return ShopInBitOrderStatus.pending; @@ -323,6 +342,8 @@ class ShopInBitOrderModel extends ChangeNotifier { return ShopInBitOrderStatus.cancelled; case TicketState.refunded: return ShopInBitOrderStatus.refunded; + case TicketState.unknown: + return null; } } } diff --git a/lib/pages/shopinbit/shopinbit_ticket_detail.dart b/lib/pages/shopinbit/shopinbit_ticket_detail.dart index 85ceb97cf..1ec904856 100644 --- a/lib/pages/shopinbit/shopinbit_ticket_detail.dart +++ b/lib/pages/shopinbit/shopinbit_ticket_detail.dart @@ -134,9 +134,13 @@ class _ShopInBitTicketDetailState extends State { } if (!statusResp.hasError && statusResp.value != null) { - widget.model.status = ShopInBitOrderModel.statusFromTicketState( + final mapped = ShopInBitOrderModel.statusFromTicketState( statusResp.value!.state, ); + // Always preserve the raw API string, even when mapping fails, so + // it can be recovered later. + widget.model.statusRaw = statusResp.value!.stateRaw; + if (mapped != null) widget.model.status = mapped; } if (widget.model.status == ShopInBitOrderStatus.offerAvailable && diff --git a/lib/pages/shopinbit/shopinbit_tickets_view.dart b/lib/pages/shopinbit/shopinbit_tickets_view.dart index ce62d3be3..208e382b9 100644 --- a/lib/pages/shopinbit/shopinbit_tickets_view.dart +++ b/lib/pages/shopinbit/shopinbit_tickets_view.dart @@ -127,9 +127,11 @@ class _ShopInBitTicketsViewState extends State { final statusResp = await service.client.getTicketStatus(ref.id); if (statusResp.hasError || statusResp.value == null) continue; - _tickets[localIdx].status = ShopInBitOrderModel.statusFromTicketState( + final mapped = ShopInBitOrderModel.statusFromTicketState( statusResp.value!.state, ); + _tickets[localIdx].statusRaw = statusResp.value!.stateRaw; + if (mapped != null) _tickets[localIdx].status = mapped; if (_tickets[localIdx].status == ShopInBitOrderStatus.offerAvailable && (_tickets[localIdx].offerProductName == null || diff --git a/lib/services/shopinbit/src/models/ticket.dart b/lib/services/shopinbit/src/models/ticket.dart index eec6dd360..9476da6e9 100644 --- a/lib/services/shopinbit/src/models/ticket.dart +++ b/lib/services/shopinbit/src/models/ticket.dart @@ -1,3 +1,5 @@ +import '../../../../utilities/logger.dart'; + enum TicketState { newTicket('NEW'), checking('CHECKING'), @@ -11,16 +13,25 @@ enum TicketState { replyNeeded('REPLY NEEDED'), closed('CLOSED'), closedCancelled('CLOSED/CANCELLED'), - merged('MERGED'); + merged('MERGED'), + // Sentinel for any state string the API returns that this client does not + // recognise (e.g. the API added a new state, or renamed an existing one). + // Callers must handle this explicitly: treat as "do not trust", do not + // overwrite previously known good state with it. + unknown('UNKNOWN'); final String value; const TicketState(this.value); static TicketState fromString(String s) { - return TicketState.values.firstWhere( - (e) => e.value == s, - orElse: () => TicketState.newTicket, + for (final e in TicketState.values) { + if (e.value == s) return e; + } + Logging.instance.w( + "ShopInBit: unrecognised TicketState '$s' from API: " + "mapping to TicketState.unknown", ); + return TicketState.unknown; } } @@ -38,6 +49,10 @@ class TicketRef { class TicketStatus { final int ticketId; final TicketState state; + // The raw 'state' string returned by the API. Preserved verbatim so that + // unknown / renamed states can be re-derived later via a client update, + // rather than being lost to TicketState.unknown. + final String stateRaw; final DateTime updatedAt; final DateTime? lastAgentMessageAt; final String? paymentInvoiceStatus; @@ -46,6 +61,7 @@ class TicketStatus { TicketStatus({ required this.ticketId, required this.state, + required this.stateRaw, required this.updatedAt, this.lastAgentMessageAt, this.paymentInvoiceStatus, @@ -53,9 +69,11 @@ class TicketStatus { }); factory TicketStatus.fromJson(Map json) { + final rawState = json['state'] as String; return TicketStatus( ticketId: _toInt(json['ticket_id']), - state: TicketState.fromString(json['state'] as String), + state: TicketState.fromString(rawState), + stateRaw: rawState, updatedAt: DateTime.parse(json['updated_at'] as String), lastAgentMessageAt: json['last_agent_message_at'] != null ? DateTime.parse(json['last_agent_message_at'] as String) diff --git a/lib/services/shopinbit/src/models/webhook_event.dart b/lib/services/shopinbit/src/models/webhook_event.dart index 7bf41694e..e1ff040f3 100644 --- a/lib/services/shopinbit/src/models/webhook_event.dart +++ b/lib/services/shopinbit/src/models/webhook_event.dart @@ -1,15 +1,25 @@ +import '../../../../utilities/logger.dart'; + enum WebhookEventType { ticketStateChanged('ticket.state_changed'), - ticketMessageCreated('ticket.message_created'); + ticketMessageCreated('ticket.message_created'), + // Sentinel for any webhook event_type the API sends that this client does + // not recognise. Callers MUST drop these events rather than dispatch them: + // coercing an unknown event onto a known handler is worse than ignoring it. + unknown('UNKNOWN'); final String value; const WebhookEventType(this.value); static WebhookEventType fromString(String s) { - return WebhookEventType.values.firstWhere( - (e) => e.value == s, - orElse: () => WebhookEventType.ticketStateChanged, + for (final e in WebhookEventType.values) { + if (e.value == s) return e; + } + Logging.instance.w( + "ShopInBit: unrecognised WebhookEventType '$s' from API: " + "mapping to WebhookEventType.unknown (event will be dropped)", ); + return WebhookEventType.unknown; } } From 1c515e1e27f16566cdb57fa497a98dd46acfd3f2 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 25 May 2026 10:25:21 -0600 Subject: [PATCH 2/2] fix: merge conflict clean up --- lib/db/drift/shared_db/shared_database.g.dart | 68 +++++++++++++++++++ .../shared_db/tables/shopin_bit_tickets.dart | 1 + .../shopinbit/shopinbit_order_model.dart | 1 - .../shopinbit/shopinbit_orders_service.dart | 3 +- 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/lib/db/drift/shared_db/shared_database.g.dart b/lib/db/drift/shared_db/shared_database.g.dart index 24a3c8351..28c4c3991 100644 --- a/lib/db/drift/shared_db/shared_database.g.dart +++ b/lib/db/drift/shared_db/shared_database.g.dart @@ -538,6 +538,17 @@ class $ShopInBitTicketsTable extends ShopInBitTickets ).withConverter( $ShopInBitTicketsTable.$converterstatus, ); + static const VerificationMeta _statusRawMeta = const VerificationMeta( + 'statusRaw', + ); + @override + late final GeneratedColumn statusRaw = GeneratedColumn( + 'status_raw', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _requestDescriptionMeta = const VerificationMeta('requestDescription'); @override @@ -762,6 +773,7 @@ class $ShopInBitTicketsTable extends ShopInBitTickets displayName, category, status, + statusRaw, requestDescription, deliveryCountry, offerProductName, @@ -813,6 +825,12 @@ class $ShopInBitTicketsTable extends ShopInBitTickets } else if (isInserting) { context.missing(_displayNameMeta); } + if (data.containsKey('status_raw')) { + context.handle( + _statusRawMeta, + statusRaw.isAcceptableOrUnknown(data['status_raw']!, _statusRawMeta), + ); + } if (data.containsKey('request_description')) { context.handle( _requestDescriptionMeta, @@ -1020,6 +1038,10 @@ class $ShopInBitTicketsTable extends ShopInBitTickets data['${effectivePrefix}status'], )!, ), + statusRaw: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}status_raw'], + ), requestDescription: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}request_description'], @@ -1121,6 +1143,7 @@ class ShopInBitTicket extends DataClass implements Insertable { final String displayName; final ShopInBitCategory category; final ShopInBitOrderStatus status; + final String? statusRaw; final String requestDescription; final String deliveryCountry; final String? offerProductName; @@ -1145,6 +1168,7 @@ class ShopInBitTicket extends DataClass implements Insertable { required this.displayName, required this.category, required this.status, + this.statusRaw, required this.requestDescription, required this.deliveryCountry, this.offerProductName, @@ -1180,6 +1204,9 @@ class ShopInBitTicket extends DataClass implements Insertable { $ShopInBitTicketsTable.$converterstatus.toSql(status), ); } + if (!nullToAbsent || statusRaw != null) { + map['status_raw'] = Variable(statusRaw); + } map['request_description'] = Variable(requestDescription); map['delivery_country'] = Variable(deliveryCountry); if (!nullToAbsent || offerProductName != null) { @@ -1228,6 +1255,9 @@ class ShopInBitTicket extends DataClass implements Insertable { displayName: Value(displayName), category: Value(category), status: Value(status), + statusRaw: statusRaw == null && nullToAbsent + ? const Value.absent() + : Value(statusRaw), requestDescription: Value(requestDescription), deliveryCountry: Value(deliveryCountry), offerProductName: offerProductName == null && nullToAbsent @@ -1278,6 +1308,7 @@ class ShopInBitTicket extends DataClass implements Insertable { status: $ShopInBitTicketsTable.$converterstatus.fromJson( serializer.fromJson(json['status']), ), + statusRaw: serializer.fromJson(json['statusRaw']), requestDescription: serializer.fromJson( json['requestDescription'], ), @@ -1323,6 +1354,7 @@ class ShopInBitTicket extends DataClass implements Insertable { 'status': serializer.toJson( $ShopInBitTicketsTable.$converterstatus.toJson(status), ), + 'statusRaw': serializer.toJson(statusRaw), 'requestDescription': serializer.toJson(requestDescription), 'deliveryCountry': serializer.toJson(deliveryCountry), 'offerProductName': serializer.toJson(offerProductName), @@ -1356,6 +1388,7 @@ class ShopInBitTicket extends DataClass implements Insertable { String? displayName, ShopInBitCategory? category, ShopInBitOrderStatus? status, + Value statusRaw = const Value.absent(), String? requestDescription, String? deliveryCountry, Value offerProductName = const Value.absent(), @@ -1380,6 +1413,7 @@ class ShopInBitTicket extends DataClass implements Insertable { displayName: displayName ?? this.displayName, category: category ?? this.category, status: status ?? this.status, + statusRaw: statusRaw.present ? statusRaw.value : this.statusRaw, requestDescription: requestDescription ?? this.requestDescription, deliveryCountry: deliveryCountry ?? this.deliveryCountry, offerProductName: offerProductName.present @@ -1420,6 +1454,7 @@ class ShopInBitTicket extends DataClass implements Insertable { : this.displayName, category: data.category.present ? data.category.value : this.category, status: data.status.present ? data.status.value : this.status, + statusRaw: data.statusRaw.present ? data.statusRaw.value : this.statusRaw, requestDescription: data.requestDescription.present ? data.requestDescription.value : this.requestDescription, @@ -1483,6 +1518,7 @@ class ShopInBitTicket extends DataClass implements Insertable { ..write('displayName: $displayName, ') ..write('category: $category, ') ..write('status: $status, ') + ..write('statusRaw: $statusRaw, ') ..write('requestDescription: $requestDescription, ') ..write('deliveryCountry: $deliveryCountry, ') ..write('offerProductName: $offerProductName, ') @@ -1512,6 +1548,7 @@ class ShopInBitTicket extends DataClass implements Insertable { displayName, category, status, + statusRaw, requestDescription, deliveryCountry, offerProductName, @@ -1540,6 +1577,7 @@ class ShopInBitTicket extends DataClass implements Insertable { other.displayName == this.displayName && other.category == this.category && other.status == this.status && + other.statusRaw == this.statusRaw && other.requestDescription == this.requestDescription && other.deliveryCountry == this.deliveryCountry && other.offerProductName == this.offerProductName && @@ -1566,6 +1604,7 @@ class ShopInBitTicketsCompanion extends UpdateCompanion { final Value displayName; final Value category; final Value status; + final Value statusRaw; final Value requestDescription; final Value deliveryCountry; final Value offerProductName; @@ -1591,6 +1630,7 @@ class ShopInBitTicketsCompanion extends UpdateCompanion { this.displayName = const Value.absent(), this.category = const Value.absent(), this.status = const Value.absent(), + this.statusRaw = const Value.absent(), this.requestDescription = const Value.absent(), this.deliveryCountry = const Value.absent(), this.offerProductName = const Value.absent(), @@ -1617,6 +1657,7 @@ class ShopInBitTicketsCompanion extends UpdateCompanion { required String displayName, required ShopInBitCategory category, required ShopInBitOrderStatus status, + this.statusRaw = const Value.absent(), required String requestDescription, required String deliveryCountry, this.offerProductName = const Value.absent(), @@ -1658,6 +1699,7 @@ class ShopInBitTicketsCompanion extends UpdateCompanion { Expression? displayName, Expression? category, Expression? status, + Expression? statusRaw, Expression? requestDescription, Expression? deliveryCountry, Expression? offerProductName, @@ -1684,6 +1726,7 @@ class ShopInBitTicketsCompanion extends UpdateCompanion { if (displayName != null) 'display_name': displayName, if (category != null) 'category': category, if (status != null) 'status': status, + if (statusRaw != null) 'status_raw': statusRaw, if (requestDescription != null) 'request_description': requestDescription, if (deliveryCountry != null) 'delivery_country': deliveryCountry, if (offerProductName != null) 'offer_product_name': offerProductName, @@ -1717,6 +1760,7 @@ class ShopInBitTicketsCompanion extends UpdateCompanion { Value? displayName, Value? category, Value? status, + Value? statusRaw, Value? requestDescription, Value? deliveryCountry, Value? offerProductName, @@ -1743,6 +1787,7 @@ class ShopInBitTicketsCompanion extends UpdateCompanion { displayName: displayName ?? this.displayName, category: category ?? this.category, status: status ?? this.status, + statusRaw: statusRaw ?? this.statusRaw, requestDescription: requestDescription ?? this.requestDescription, deliveryCountry: deliveryCountry ?? this.deliveryCountry, offerProductName: offerProductName ?? this.offerProductName, @@ -1786,6 +1831,9 @@ class ShopInBitTicketsCompanion extends UpdateCompanion { $ShopInBitTicketsTable.$converterstatus.toSql(status.value), ); } + if (statusRaw.present) { + map['status_raw'] = Variable(statusRaw.value); + } if (requestDescription.present) { map['request_description'] = Variable(requestDescription.value); } @@ -1864,6 +1912,7 @@ class ShopInBitTicketsCompanion extends UpdateCompanion { ..write('displayName: $displayName, ') ..write('category: $category, ') ..write('status: $status, ') + ..write('statusRaw: $statusRaw, ') ..write('requestDescription: $requestDescription, ') ..write('deliveryCountry: $deliveryCountry, ') ..write('offerProductName: $offerProductName, ') @@ -2230,6 +2279,7 @@ typedef $$ShopInBitTicketsTableCreateCompanionBuilder = required String displayName, required ShopInBitCategory category, required ShopInBitOrderStatus status, + Value statusRaw, required String requestDescription, required String deliveryCountry, Value offerProductName, @@ -2257,6 +2307,7 @@ typedef $$ShopInBitTicketsTableUpdateCompanionBuilder = Value displayName, Value category, Value status, + Value statusRaw, Value requestDescription, Value deliveryCountry, Value offerProductName, @@ -2314,6 +2365,11 @@ class $$ShopInBitTicketsTableFilterComposer builder: (column) => ColumnWithTypeConverterFilters(column), ); + ColumnFilters get statusRaw => $composableBuilder( + column: $table.statusRaw, + builder: (column) => ColumnFilters(column), + ); + ColumnFilters get requestDescription => $composableBuilder( column: $table.requestDescription, builder: (column) => ColumnFilters(column), @@ -2444,6 +2500,11 @@ class $$ShopInBitTicketsTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get statusRaw => $composableBuilder( + column: $table.statusRaw, + builder: (column) => ColumnOrderings(column), + ); + ColumnOrderings get requestDescription => $composableBuilder( column: $table.requestDescription, builder: (column) => ColumnOrderings(column), @@ -2563,6 +2624,9 @@ class $$ShopInBitTicketsTableAnnotationComposer GeneratedColumnWithTypeConverter get status => $composableBuilder(column: $table.status, builder: (column) => column); + GeneratedColumn get statusRaw => + $composableBuilder(column: $table.statusRaw, builder: (column) => column); + GeneratedColumn get requestDescription => $composableBuilder( column: $table.requestDescription, builder: (column) => column, @@ -2697,6 +2761,7 @@ class $$ShopInBitTicketsTableTableManager Value displayName = const Value.absent(), Value category = const Value.absent(), Value status = const Value.absent(), + Value statusRaw = const Value.absent(), Value requestDescription = const Value.absent(), Value deliveryCountry = const Value.absent(), Value offerProductName = const Value.absent(), @@ -2723,6 +2788,7 @@ class $$ShopInBitTicketsTableTableManager displayName: displayName, category: category, status: status, + statusRaw: statusRaw, requestDescription: requestDescription, deliveryCountry: deliveryCountry, offerProductName: offerProductName, @@ -2750,6 +2816,7 @@ class $$ShopInBitTicketsTableTableManager required String displayName, required ShopInBitCategory category, required ShopInBitOrderStatus status, + Value statusRaw = const Value.absent(), required String requestDescription, required String deliveryCountry, Value offerProductName = const Value.absent(), @@ -2775,6 +2842,7 @@ class $$ShopInBitTicketsTableTableManager displayName: displayName, category: category, status: status, + statusRaw: statusRaw, requestDescription: requestDescription, deliveryCountry: deliveryCountry, offerProductName: offerProductName, diff --git a/lib/db/drift/shared_db/tables/shopin_bit_tickets.dart b/lib/db/drift/shared_db/tables/shopin_bit_tickets.dart index b8afcc969..450053a20 100644 --- a/lib/db/drift/shared_db/tables/shopin_bit_tickets.dart +++ b/lib/db/drift/shared_db/tables/shopin_bit_tickets.dart @@ -12,6 +12,7 @@ class ShopInBitTickets extends Table { IntColumn get category => intEnum()(); IntColumn get status => intEnum()(); + TextColumn get statusRaw => text().nullable()(); TextColumn get requestDescription => text()(); TextColumn get deliveryCountry => text()(); diff --git a/lib/models/shopinbit/shopinbit_order_model.dart b/lib/models/shopinbit/shopinbit_order_model.dart index 3b314eb36..14b530475 100644 --- a/lib/models/shopinbit/shopinbit_order_model.dart +++ b/lib/models/shopinbit/shopinbit_order_model.dart @@ -324,7 +324,6 @@ class ShopInBitOrderModel extends ChangeNotifier { ) .toList(); - static ShopInBitOrderModel fromIsarTicket(ShopInBitTicket ticket) { return ShopInBitOrderModel() .._displayName = ticket.displayName .._category = ticket.category diff --git a/lib/services/shopinbit/shopinbit_orders_service.dart b/lib/services/shopinbit/shopinbit_orders_service.dart index 8651b4a2c..204bb25c3 100644 --- a/lib/services/shopinbit/shopinbit_orders_service.dart +++ b/lib/services/shopinbit/shopinbit_orders_service.dart @@ -82,7 +82,8 @@ class ShopInBitOrdersService extends ChangeNotifier { final newStatus = ShopInBitOrderModel.statusFromTicketState( statusResp.value!.state, ); - if (model.status != newStatus) { + model.statusRaw = statusResp.value!.stateRaw; + if (model.status != newStatus && newStatus != null) { model.status = newStatus; changed = true; }