enum ProductType { simple, variable, grouped, external, digital, // specific logic might be needed, often just 'simple' + virtual/downloadable properties downloadable, // usually property of simple/variable virtual, // usually property of simple/variable subscription, variableSubscription, // 'variable-subscription' bundle, composite, bookable, unknown, } abstract class Product { final int id; final String name; final ProductType type; final String status; // publish, private, draft, pending, future final String? sku; final String? price; final String? regularPrice; final String? salePrice; final String stockStatus; // instock, outofstock, onbackorder final bool manageStock; final int? stockQuantity; final List images; final String description; final String shortDescription; final List metaData; Product({ required this.id, required this.name, required this.type, required this.status, this.sku, this.price, this.regularPrice, this.salePrice, required this.stockStatus, this.manageStock = false, this.stockQuantity, this.images = const [], this.description = '', this.shortDescription = '', this.metaData = const [], }); factory Product.fromJson(Map json) { final typeStr = json['type'] as String? ?? 'simple'; final type = _parseType(typeStr); switch (type) { case ProductType.variable: return VariableProduct.fromJson(json, type); case ProductType.grouped: return GroupedProduct.fromJson(json, type); case ProductType.external: return ExternalProduct.fromJson(json, type); // Add other specific classes as needed. // For now, map others to SimpleProduct or a specific generic fallback if they don't have unique fields we critically need right now. default: // 'simple', 'booking', 'subscription', etc. unless they get their own class return SimpleProduct.fromJson(json, type); } } static ProductType _parseType(String type) { switch (type) { case 'simple': return ProductType.simple; case 'variable': return ProductType.variable; case 'grouped': return ProductType.grouped; case 'external': return ProductType.external; case 'subscription': return ProductType.subscription; case 'variable-subscription': return ProductType.variableSubscription; case 'bundle': return ProductType.bundle; case 'composite': return ProductType.composite; case 'booking': return ProductType.bookable; default: return ProductType.unknown; } } Map toJson() { return { 'id': id, 'name': name, 'type': _typeToString(type), 'status': status, 'sku': sku, 'regular_price': regularPrice, 'sale_price': salePrice, 'stock_status': stockStatus, 'manage_stock': manageStock, 'stock_quantity': stockQuantity, 'images': images.map((i) => i.toJson()).toList(), 'description': description, 'short_description': shortDescription, 'meta_data': metaData, }; } String _typeToString(ProductType t) { // simplified reverse mapping switch (t) { case ProductType.simple: return 'simple'; case ProductType.variable: return 'variable'; case ProductType.grouped: return 'grouped'; case ProductType.external: return 'external'; case ProductType.subscription: return 'subscription'; case ProductType.variableSubscription: return 'variable-subscription'; case ProductType.bundle: return 'bundle'; case ProductType.composite: return 'composite'; case ProductType.bookable: return 'booking'; default: return 'simple'; } } } class ProductImage { final int id; final String src; final String name; final String alt; ProductImage({ required this.id, required this.src, required this.name, required this.alt, }); factory ProductImage.fromJson(Map json) { return ProductImage( id: json['id'] ?? 0, src: json['src'] ?? '', name: json['name'] ?? '', alt: json['alt'] ?? '', ); } Map toJson() => { 'id': id, 'src': src, 'name': name, 'alt': alt, }; } class SimpleProduct extends Product { final bool virtual; final bool downloadable; SimpleProduct({ required super.id, required super.name, required super.type, required super.status, super.sku, super.price, super.regularPrice, super.salePrice, required super.stockStatus, super.manageStock, super.stockQuantity, super.images, super.description, super.shortDescription, super.metaData, this.virtual = false, this.downloadable = false, }); factory SimpleProduct.fromJson(Map json, ProductType type) { return SimpleProduct( id: json['id'], name: json['name'] ?? '', type: type, status: json['status'] ?? 'publish', sku: json['sku'], price: json['price']?.toString(), // API returns string or number? Usually string in WC regularPrice: json['regular_price']?.toString(), salePrice: json['sale_price']?.toString(), stockStatus: json['stock_status'] ?? 'instock', manageStock: json['manage_stock'] ?? false, stockQuantity: json['stock_quantity'], images: (json['images'] as List?)?.map((e) => ProductImage.fromJson(e)).toList() ?? [], description: json['description'] ?? '', shortDescription: json['short_description'] ?? '', metaData: List.from(json['meta_data'] ?? []), virtual: json['virtual'] ?? false, downloadable: json['downloadable'] ?? false, ); } @override Map toJson() { final m = super.toJson(); m['virtual'] = virtual; m['downloadable'] = downloadable; return m; } } class VariableProduct extends Product { final List variations; VariableProduct({ required super.id, required super.name, required super.type, required super.status, super.sku, super.price, super.regularPrice, super.salePrice, required super.stockStatus, super.manageStock, super.stockQuantity, super.images, super.description, super.shortDescription, super.metaData, this.variations = const [], }); factory VariableProduct.fromJson(Map json, ProductType type) { return VariableProduct( id: json['id'], name: json['name'] ?? '', type: type, status: json['status'] ?? 'publish', sku: json['sku'], price: json['price']?.toString(), regularPrice: json['regular_price']?.toString(), salePrice: json['sale_price']?.toString(), stockStatus: json['stock_status'] ?? 'instock', manageStock: json['manage_stock'] ?? false, stockQuantity: json['stock_quantity'], images: (json['images'] as List?)?.map((e) => ProductImage.fromJson(e)).toList() ?? [], description: json['description'] ?? '', shortDescription: json['short_description'] ?? '', metaData: List.from(json['meta_data'] ?? []), variations: (json['variations'] as List?)?.cast() ?? [], ); } @override Map toJson() { final m = super.toJson(); m['variations'] = variations; return m; } } class Variation { final int id; final String sku; final String? price; final String? regularPrice; final String? salePrice; final String stockStatus; final bool manageStock; final int? stockQuantity; final List> attributes; // e.g. [{id: 0, name: 'Color', option: 'Blue'}] final ProductImage? image; Variation({ required this.id, this.sku = '', this.price, this.regularPrice, this.salePrice, this.stockStatus = 'instock', this.manageStock = false, this.stockQuantity, this.attributes = const [], this.image, }); factory Variation.fromJson(Map json) { return Variation( id: json['id'], sku: json['sku'] ?? '', price: json['price']?.toString(), regularPrice: json['regular_price']?.toString(), salePrice: json['sale_price']?.toString(), stockStatus: json['stock_status'] ?? 'instock', manageStock: json['manage_stock'] ?? false, stockQuantity: json['stock_quantity'], attributes: List>.from(json['attributes'] ?? []), image: json['image'] != null ? ProductImage.fromJson(json['image']) : null, ); } Map toJson() => { 'id': id, 'sku': sku, 'regular_price': regularPrice, 'sale_price': salePrice, 'stock_status': stockStatus, 'manage_stock': manageStock, 'stock_quantity': stockQuantity, 'attributes': attributes, 'image': image?.toJson(), }; } class GroupedProduct extends Product { final List groupedProducts; GroupedProduct({ required super.id, required super.name, required super.type, required super.status, super.sku, super.price, super.regularPrice, super.salePrice, required super.stockStatus, super.manageStock, super.stockQuantity, super.images, super.description, super.shortDescription, super.metaData, this.groupedProducts = const [], }); factory GroupedProduct.fromJson(Map json, ProductType type) { return GroupedProduct( id: json['id'], name: json['name'] ?? '', type: type, status: json['status'] ?? 'publish', sku: json['sku'], price: json['price']?.toString(), regularPrice: json['regular_price']?.toString(), salePrice: json['sale_price']?.toString(), stockStatus: json['stock_status'] ?? 'instock', manageStock: json['manage_stock'] ?? false, stockQuantity: json['stock_quantity'], images: (json['images'] as List?)?.map((e) => ProductImage.fromJson(e)).toList() ?? [], description: json['description'] ?? '', shortDescription: json['short_description'] ?? '', metaData: List.from(json['meta_data'] ?? []), groupedProducts: (json['grouped_products'] as List?)?.cast() ?? [], ); } @override Map toJson() { final m = super.toJson(); m['grouped_products'] = groupedProducts; return m; } } class ExternalProduct extends Product { final String externalUrl; final String buttonText; ExternalProduct({ required super.id, required super.name, required super.type, required super.status, super.sku, super.price, super.regularPrice, super.salePrice, required super.stockStatus, super.manageStock, super.stockQuantity, super.images, super.description, super.shortDescription, super.metaData, this.externalUrl = '', this.buttonText = '', }); factory ExternalProduct.fromJson(Map json, ProductType type) { return ExternalProduct( id: json['id'], name: json['name'] ?? '', type: type, status: json['status'] ?? 'publish', sku: json['sku'], price: json['price']?.toString(), regularPrice: json['regular_price']?.toString(), salePrice: json['sale_price']?.toString(), stockStatus: json['stock_status'] ?? 'instock', manageStock: json['manage_stock'] ?? false, stockQuantity: json['stock_quantity'], images: (json['images'] as List?)?.map((e) => ProductImage.fromJson(e)).toList() ?? [], description: json['description'] ?? '', shortDescription: json['short_description'] ?? '', metaData: List.from(json['meta_data'] ?? []), externalUrl: json['external_url'] ?? '', buttonText: json['button_text'] ?? '', ); } @override Map toJson() { final m = super.toJson(); m['external_url'] = externalUrl; m['button_text'] = buttonText; return m; } }