Fixed inventory items photo thumbnail issue
This commit is contained in:
@@ -39,6 +39,7 @@ public class InventoryItem {
|
||||
private Long vendorId;
|
||||
private Long branchId;
|
||||
private String purchaseRef;
|
||||
private String photoUrl;
|
||||
@Builder.Default
|
||||
private String status = "AVAILABLE";
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@@ -55,6 +55,7 @@ public class InventoryItemService {
|
||||
if (updatedItem.getVendorId() != null) existingItem.setVendorId(updatedItem.getVendorId());
|
||||
if (updatedItem.getBranchId() != null) existingItem.setBranchId(updatedItem.getBranchId());
|
||||
if (updatedItem.getPurchaseRef() != null) existingItem.setPurchaseRef(updatedItem.getPurchaseRef());
|
||||
if (updatedItem.getPhotoUrl() != null) existingItem.setPhotoUrl(updatedItem.getPhotoUrl());
|
||||
if (updatedItem.getStatus() != null) existingItem.setStatus(updatedItem.getStatus());
|
||||
existingItem.setUpdatedAt(LocalDateTime.now());
|
||||
return inventoryItemRepository.save(existingItem);
|
||||
|
||||
@@ -195,6 +195,7 @@ public class PurchaseOrderService {
|
||||
.huid(item.getHuid())
|
||||
.grossWeight(item.getWeight())
|
||||
.netWeight(item.getWeight())
|
||||
.photoUrl(item.getPhotoUrl())
|
||||
.status("AVAILABLE")
|
||||
.build();
|
||||
|
||||
|
||||
@@ -383,6 +383,7 @@ CREATE TABLE IF NOT EXISTS inventory_items (
|
||||
vendor_id INTEGER REFERENCES vendors(id) ON DELETE SET NULL,
|
||||
branch_id INTEGER,
|
||||
purchase_ref VARCHAR(100),
|
||||
photo_url VARCHAR(1024),
|
||||
status VARCHAR(50) DEFAULT 'AVAILABLE',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
|
||||
@@ -21,6 +21,7 @@ class InventoryItem {
|
||||
final int? vendorId;
|
||||
final int? branchId;
|
||||
final String? purchaseRef;
|
||||
final String? photoUrl;
|
||||
final String? status;
|
||||
final DateTime? createdAt;
|
||||
|
||||
@@ -47,6 +48,7 @@ class InventoryItem {
|
||||
this.vendorId,
|
||||
this.branchId,
|
||||
this.purchaseRef,
|
||||
this.photoUrl,
|
||||
this.status,
|
||||
this.createdAt,
|
||||
});
|
||||
@@ -75,6 +77,7 @@ class InventoryItem {
|
||||
vendorId: json['vendorId'],
|
||||
branchId: json['branchId'],
|
||||
purchaseRef: json['purchaseRef'],
|
||||
photoUrl: json['photoUrl'] ?? json['photo_url'],
|
||||
status: json['status'],
|
||||
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/providers/purchase_orders_provider.dart';
|
||||
import 'package:kifi_app/core/utils/purity_utils.dart';
|
||||
import '../../transactions/presentation/widgets/attachment_gallery_screen.dart';
|
||||
|
||||
class StockLedgerTab extends ConsumerStatefulWidget {
|
||||
final Product product;
|
||||
@@ -26,6 +27,18 @@ class _StockLedgerTabState extends ConsumerState<StockLedgerTab> {
|
||||
bool _isLoading = true;
|
||||
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
|
||||
void 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 currentPrice = weight * currentRate;
|
||||
final gainLoss = currentPrice - purchasePrice;
|
||||
@@ -188,30 +226,93 @@ class _StockLedgerTabState extends ConsumerState<StockLedgerTab> {
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: FutureBuilder<String?>(
|
||||
FutureBuilder<String?>(
|
||||
future: DioClient().storage.read(key: 'jwt_token'),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && widget.product.imageIds.isNotEmpty) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.network(
|
||||
'${DioClient().dio.options.baseUrl}/inventory/products/${widget.product.id}/images/${widget.product.imageIds.first}/content',
|
||||
fit: BoxFit.cover,
|
||||
headers: {'Authorization': 'Bearer ${snapshot.data}'},
|
||||
final hasImage = finalDisplayUrl != null;
|
||||
return GestureDetector(
|
||||
onTap: hasImage
|
||||
? () {
|
||||
final token = snapshot.data;
|
||||
final headers = (token != null && isProductFallback)
|
||||
? {'Authorization': 'Bearer $token'}
|
||||
: null;
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => AttachmentGalleryScreen(
|
||||
images: [
|
||||
NetworkImage(
|
||||
finalDisplayUrl!,
|
||||
headers: headers,
|
||||
),
|
||||
],
|
||||
initialIndex: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const Icon(LucideIcons.image, color: Colors.grey, size: 20);
|
||||
},
|
||||
: 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(
|
||||
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),
|
||||
Expanded(
|
||||
child: Column(
|
||||
|
||||
Reference in New Issue
Block a user