package main import ( "encoding/json" "fmt" "time" "github.com/hyperledger/fabric-contract-api-go/contractapi" ) // Plant defines your asset structure type Plant struct { Barcode string `json:"barcode"` LicenseID string `json:"license_id"` OperatorName string `json:"operator_name"` OperatorID string `json:"operator_id"` ZipCode string `json:"zip_code"` Manufacturer string `json:"manufacturer"` PotencyTHC string `json:"potency_thc,omitempty"` BatchNumber string `json:"batch_number"` LifecycleStage string `json:"lifecycle_stage"` GPSLocation string `json:"gps_location"` History []HistoryEntry `json:"history,omitempty"` UpdatedAt string `json:"updated_at"` Status string `json:"status"` // "active", "flagged", "destroyed" } type HistoryEntry struct { LifecycleStage string `json:"lifecycle_stage"` UpdatedAt string `json:"updated_at"` OperatorName string `json:"operator_name"` OperatorID string `json:"operator_id"` } type SmartContract struct { contractapi.Contract } // CreateOrUpdatePlant puts or updates a plant record by barcode func (s *SmartContract) CreateOrUpdatePlant(ctx contractapi.TransactionContextInterface, barcode string, plantJSON string) error { var plant Plant err := json.Unmarshal([]byte(plantJSON), &plant) if err != nil { return fmt.Errorf("failed to decode plant data: %v", err) } plant.Barcode = barcode plant.UpdatedAt = time.Now().UTC().Format(time.RFC3339) if plant.Status == "" { plant.Status = "active" } data, _ := ctx.GetStub().GetState(barcode) if data != nil { var existing Plant if err := json.Unmarshal(data, &existing); err == nil { plant.History = existing.History plant.Status = existing.Status // preserve if present } } entry := HistoryEntry{ LifecycleStage: plant.LifecycleStage, UpdatedAt: plant.UpdatedAt, OperatorName: plant.OperatorName, OperatorID: plant.OperatorID, } plant.History = append(plant.History, entry) plantBytes, err := json.Marshal(plant) if err != nil { return fmt.Errorf("failed to marshal plant: %v", err) } return ctx.GetStub().PutState(barcode, plantBytes) } // GetPlantByBarcode returns a plant by barcode func (s *SmartContract) GetPlantByBarcode(ctx contractapi.TransactionContextInterface, barcode string) (*Plant, error) { data, err := ctx.GetStub().GetState(barcode) if err != nil { return nil, fmt.Errorf("failed to read from ledger: %v", err) } if data == nil { return nil, fmt.Errorf("plant not found") } var plant Plant err = json.Unmarshal(data, &plant) if err != nil { return nil, fmt.Errorf("unmarshal error: %v", err) } return &plant, nil } // UpdatePlantStatus flags/destroys a plant func (s *SmartContract) UpdatePlantStatus(ctx contractapi.TransactionContextInterface, barcode string, status string) error { data, err := ctx.GetStub().GetState(barcode) if err != nil || data == nil { return fmt.Errorf("plant not found or failed to read: %v", err) } var plant Plant if err := json.Unmarshal(data, &plant); err != nil { return fmt.Errorf("unmarshal error: %v", err) } plant.Status = status plant.UpdatedAt = time.Now().UTC().Format(time.RFC3339) newData, err := json.Marshal(plant) if err != nil { return fmt.Errorf("marshal error: %v", err) } return ctx.GetStub().PutState(barcode, newData) } // QueryAllPlants returns all plants (simple range) func (s *SmartContract) QueryAllPlants(ctx contractapi.TransactionContextInterface) ([]*Plant, error) { resultsIterator, err := ctx.GetStub().GetStateByRange("", "") if err != nil { return nil, err } defer resultsIterator.Close() var plants []*Plant for resultsIterator.HasNext() { kv, err := resultsIterator.Next() if err != nil { return nil, err } var plant Plant if err := json.Unmarshal(kv.Value, &plant); err == nil { plants = append(plants, &plant) } } return plants, nil } func main() { chaincode, err := contractapi.NewChaincode(&SmartContract{}) if err != nil { panic(fmt.Sprintf("Error creating chaincode: %v", err)) } if err := chaincode.Start(); err != nil { panic(fmt.Sprintf("Error starting chaincode: %v", err)) } }