Fixed inventory items photo thumbnail issue
This commit is contained in:
@@ -39,6 +39,7 @@ public class InventoryItem {
|
|||||||
private Long vendorId;
|
private Long vendorId;
|
||||||
private Long branchId;
|
private Long branchId;
|
||||||
private String purchaseRef;
|
private String purchaseRef;
|
||||||
|
private String photoUrl;
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private String status = "AVAILABLE";
|
private String status = "AVAILABLE";
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ public class InventoryItemService {
|
|||||||
if (updatedItem.getVendorId() != null) existingItem.setVendorId(updatedItem.getVendorId());
|
if (updatedItem.getVendorId() != null) existingItem.setVendorId(updatedItem.getVendorId());
|
||||||
if (updatedItem.getBranchId() != null) existingItem.setBranchId(updatedItem.getBranchId());
|
if (updatedItem.getBranchId() != null) existingItem.setBranchId(updatedItem.getBranchId());
|
||||||
if (updatedItem.getPurchaseRef() != null) existingItem.setPurchaseRef(updatedItem.getPurchaseRef());
|
if (updatedItem.getPurchaseRef() != null) existingItem.setPurchaseRef(updatedItem.getPurchaseRef());
|
||||||
|
if (updatedItem.getPhotoUrl() != null) existingItem.setPhotoUrl(updatedItem.getPhotoUrl());
|
||||||
if (updatedItem.getStatus() != null) existingItem.setStatus(updatedItem.getStatus());
|
if (updatedItem.getStatus() != null) existingItem.setStatus(updatedItem.getStatus());
|
||||||
existingItem.setUpdatedAt(LocalDateTime.now());
|
existingItem.setUpdatedAt(LocalDateTime.now());
|
||||||
return inventoryItemRepository.save(existingItem);
|
return inventoryItemRepository.save(existingItem);
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ public class PurchaseOrderService {
|
|||||||
.huid(item.getHuid())
|
.huid(item.getHuid())
|
||||||
.grossWeight(item.getWeight())
|
.grossWeight(item.getWeight())
|
||||||
.netWeight(item.getWeight())
|
.netWeight(item.getWeight())
|
||||||
|
.photoUrl(item.getPhotoUrl())
|
||||||
.status("AVAILABLE")
|
.status("AVAILABLE")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ CREATE TABLE IF NOT EXISTS inventory_items (
|
|||||||
vendor_id INTEGER REFERENCES vendors(id) ON DELETE SET NULL,
|
vendor_id INTEGER REFERENCES vendors(id) ON DELETE SET NULL,
|
||||||
branch_id INTEGER,
|
branch_id INTEGER,
|
||||||
purchase_ref VARCHAR(100),
|
purchase_ref VARCHAR(100),
|
||||||
|
photo_url VARCHAR(1024),
|
||||||
status VARCHAR(50) DEFAULT 'AVAILABLE',
|
status VARCHAR(50) DEFAULT 'AVAILABLE',
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class InventoryItem {
|
|||||||
final int? vendorId;
|
final int? vendorId;
|
||||||
final int? branchId;
|
final int? branchId;
|
||||||
final String? purchaseRef;
|
final String? purchaseRef;
|
||||||
|
final String? photoUrl;
|
||||||
final String? status;
|
final String? status;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ class InventoryItem {
|
|||||||
this.vendorId,
|
this.vendorId,
|
||||||
this.branchId,
|
this.branchId,
|
||||||
this.purchaseRef,
|
this.purchaseRef,
|
||||||
|
this.photoUrl,
|
||||||
this.status,
|
this.status,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
});
|
});
|
||||||
@@ -75,6 +77,7 @@ class InventoryItem {
|
|||||||
vendorId: json['vendorId'],
|
vendorId: json['vendorId'],
|
||||||
branchId: json['branchId'],
|
branchId: json['branchId'],
|
||||||
purchaseRef: json['purchaseRef'],
|
purchaseRef: json['purchaseRef'],
|
||||||
|
photoUrl: json['photoUrl'] ?? json['photo_url'],
|
||||||
status: json['status'],
|
status: json['status'],
|
||||||
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt']) : null,
|
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt']) : null,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:lucide_icons_flutter/lucide_icons.dart';
|
|||||||
import 'package:kifi_app/features/vendor/presentation/purchase_order_details_screen.dart';
|
import 'package:kifi_app/features/vendor/presentation/purchase_order_details_screen.dart';
|
||||||
import 'package:kifi_app/features/vendor/providers/purchase_orders_provider.dart';
|
import 'package:kifi_app/features/vendor/providers/purchase_orders_provider.dart';
|
||||||
import 'package:kifi_app/core/utils/purity_utils.dart';
|
import 'package:kifi_app/core/utils/purity_utils.dart';
|
||||||
|
import '../../transactions/presentation/widgets/attachment_gallery_screen.dart';
|
||||||
|
|
||||||
class StockLedgerTab extends ConsumerStatefulWidget {
|
class StockLedgerTab extends ConsumerStatefulWidget {
|
||||||
final Product product;
|
final Product product;
|
||||||
@@ -26,6 +27,18 @@ class _StockLedgerTabState extends ConsumerState<StockLedgerTab> {
|
|||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
String _selectedFilter = 'IN_STOCK'; // 'IN_STOCK', 'SOLD', 'ALL'
|
String _selectedFilter = 'IN_STOCK'; // 'IN_STOCK', 'SOLD', 'ALL'
|
||||||
|
|
||||||
|
String _resolveImageUrl(String path) {
|
||||||
|
if (path.startsWith('http://') || path.startsWith('https://')) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
final base = DioClient().dio.options.baseUrl;
|
||||||
|
final cleanPath = path.startsWith('/') ? path.substring(1) : path;
|
||||||
|
if (cleanPath.startsWith('api/kifi-v2/upload/view') || cleanPath.startsWith('upload/view')) {
|
||||||
|
return '$base/${cleanPath.replaceFirst('api/kifi-v2/', '')}';
|
||||||
|
}
|
||||||
|
return '$base/upload/view?path=${Uri.encodeComponent(cleanPath)}';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -163,6 +176,31 @@ class _StockLedgerTabState extends ConsumerState<StockLedgerTab> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve purchase item thumbnail photo
|
||||||
|
String? itemPhotoUrl = (item.photoUrl != null && item.photoUrl!.isNotEmpty) ? item.photoUrl : null;
|
||||||
|
if (itemPhotoUrl == null && item.purchaseRef != null) {
|
||||||
|
final po = purchaseOrders.where((p) => p.poNumber == item.purchaseRef).firstOrNull;
|
||||||
|
if (po != null) {
|
||||||
|
final poItem = po.items.where((i) =>
|
||||||
|
(item.huid != null && i.huid == item.huid) ||
|
||||||
|
(item.sku != null && i.sku == item.sku) ||
|
||||||
|
i.productId == widget.product.id
|
||||||
|
).firstOrNull ?? po.items.firstOrNull;
|
||||||
|
if (poItem != null && poItem.photoUrl != null && poItem.photoUrl!.isNotEmpty) {
|
||||||
|
itemPhotoUrl = poItem.photoUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String? finalDisplayUrl;
|
||||||
|
bool isProductFallback = false;
|
||||||
|
if (itemPhotoUrl != null) {
|
||||||
|
finalDisplayUrl = _resolveImageUrl(itemPhotoUrl);
|
||||||
|
} else if (widget.product.imageIds.isNotEmpty) {
|
||||||
|
finalDisplayUrl = '${DioClient().dio.options.baseUrl}/inventory/products/${widget.product.id}/images/${widget.product.imageIds.first}/content';
|
||||||
|
isProductFallback = true;
|
||||||
|
}
|
||||||
|
|
||||||
final purchasePrice = weight * purchaseRate;
|
final purchasePrice = weight * purchaseRate;
|
||||||
final currentPrice = weight * currentRate;
|
final currentPrice = weight * currentRate;
|
||||||
final gainLoss = currentPrice - purchasePrice;
|
final gainLoss = currentPrice - purchasePrice;
|
||||||
@@ -188,29 +226,92 @@ class _StockLedgerTabState extends ConsumerState<StockLedgerTab> {
|
|||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
FutureBuilder<String?>(
|
||||||
width: 40,
|
future: DioClient().storage.read(key: 'jwt_token'),
|
||||||
height: 40,
|
builder: (context, snapshot) {
|
||||||
decoration: BoxDecoration(
|
final hasImage = finalDisplayUrl != null;
|
||||||
color: Colors.grey.withValues(alpha: 0.1),
|
return GestureDetector(
|
||||||
borderRadius: BorderRadius.circular(8),
|
onTap: hasImage
|
||||||
),
|
? () {
|
||||||
child: FutureBuilder<String?>(
|
final token = snapshot.data;
|
||||||
future: DioClient().storage.read(key: 'jwt_token'),
|
final headers = (token != null && isProductFallback)
|
||||||
builder: (context, snapshot) {
|
? {'Authorization': 'Bearer $token'}
|
||||||
if (snapshot.hasData && widget.product.imageIds.isNotEmpty) {
|
: null;
|
||||||
return ClipRRect(
|
Navigator.push(
|
||||||
borderRadius: BorderRadius.circular(8),
|
context,
|
||||||
child: Image.network(
|
MaterialPageRoute(
|
||||||
'${DioClient().dio.options.baseUrl}/inventory/products/${widget.product.id}/images/${widget.product.imageIds.first}/content',
|
builder: (_) => AttachmentGalleryScreen(
|
||||||
fit: BoxFit.cover,
|
images: [
|
||||||
headers: {'Authorization': 'Bearer ${snapshot.data}'},
|
NetworkImage(
|
||||||
|
finalDisplayUrl!,
|
||||||
|
headers: headers,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
initialIndex: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: hasImage ? SystemMouseCursors.click : SystemMouseCursors.basic,
|
||||||
|
child: Container(
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.grey.withValues(alpha: 0.2),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
child: hasImage
|
||||||
}
|
? Stack(
|
||||||
return const Icon(LucideIcons.image, color: Colors.grey, size: 20);
|
children: [
|
||||||
},
|
Positioned.fill(
|
||||||
),
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(7),
|
||||||
|
child: Image.network(
|
||||||
|
finalDisplayUrl,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
headers: (snapshot.hasData && isProductFallback)
|
||||||
|
? {'Authorization': 'Bearer ${snapshot.data}'}
|
||||||
|
: null,
|
||||||
|
errorBuilder: (context, error, stackTrace) => const Icon(
|
||||||
|
LucideIcons.imageOff,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 2,
|
||||||
|
right: 2,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withValues(alpha: 0.5),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
LucideIcons.maximize2,
|
||||||
|
size: 8,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
LucideIcons.image,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|||||||
Reference in New Issue
Block a user