From 934d88ccc06ea61cdc22bf8cdce7b5f3cad194d5 Mon Sep 17 00:00:00 2001
From: "Gomez Goiri, Aitor" <aitor.gomez@tecnalia.com>
Date: Mon, 28 Feb 2022 16:49:04 +0100
Subject: [PATCH] Stats: getting and calculating total slag registered in the
 platform

---
 chaincode/controller/bid/bid_controller.go    |  2 -
 chaincode/controller/operations.go            | 20 +++-
 .../controller/stats/stats_controller.go      | 51 ++++++++++
 chaincode/controller/stats/stub.go            | 94 +++++++++++++++++++
 chaincode/main.go                             |  2 +-
 chaincode/middleware/acl.go                   | 11 +++
 chaincode/middleware/stats.go                 | 92 ++++++++++++++++++
 chaincode/model/stats.go                      | 17 ++++
 8 files changed, 282 insertions(+), 7 deletions(-)
 create mode 100644 chaincode/controller/stats/stats_controller.go
 create mode 100644 chaincode/controller/stats/stub.go
 create mode 100644 chaincode/middleware/stats.go
 create mode 100644 chaincode/model/stats.go

diff --git a/chaincode/controller/bid/bid_controller.go b/chaincode/controller/bid/bid_controller.go
index 225f65f..91d183e 100644
--- a/chaincode/controller/bid/bid_controller.go
+++ b/chaincode/controller/bid/bid_controller.go
@@ -11,7 +11,6 @@ import (
 	"git.code.tecnalia.com/ledgerbuilder/sdk/core/api"
 	"git.code.tecnalia.com/ledgerbuilder/sdk/core/controller"
 	"git.code.tecnalia.com/ledgerbuilder/sdk/core/fabric/protos"
-	"git.code.tecnalia.com/ledgerbuilder/sdk/core/util/logging"
 	"git.code.tecnalia.com/ledgerbuilder/sdk/shared"
 	"github.com/mitchellh/mapstructure"
 
@@ -53,7 +52,6 @@ type Bid struct {
 }
 
 var (
-	log						= logging.NewGoLogger("controller/transfer")
 	errInvalidInputData     = errors.New("failed to read model")
 	errParamsMissing        = errors.New("missing split params. assetId and splitParams config must be send in split request")
 	errChildrenMissmatch    = errors.New("number of requested childs and proposed id count mismatch")
diff --git a/chaincode/controller/operations.go b/chaincode/controller/operations.go
index f983790..749ce6a 100644
--- a/chaincode/controller/operations.go
+++ b/chaincode/controller/operations.go
@@ -21,6 +21,7 @@ import (
 	"git.code.tecnalia.com/traceblock/sdk/middleware"
 
 	"git.code.tecnalia.com/blockchain/hypercog/controller/bid"
+	"git.code.tecnalia.com/blockchain/hypercog/controller/stats"
 	m2 "git.code.tecnalia.com/blockchain/hypercog/middleware"
 )
 
@@ -79,7 +80,9 @@ func ContextOperations(m shared.AbstractChaincodeOperationManager) error {
 					middleware.MarkAssetAsCreated,
 					middleware.InjectAssetOwnerData,
 				},
-				nil,
+				[]shared.MiddlewareInterface{
+					m2.UpdateStats,
+				},
 				assetController.SaveAbstractAsset,
 			).
 			AddOperation(
@@ -192,6 +195,15 @@ func ContextOperations(m shared.AbstractChaincodeOperationManager) error {
 			AddBasicReadOperation("random-uuid", testController.DistributedPrngUuid).
 			AddBasicReadOperation("random-string", testController.DistributedPrngString).
 			// HyperCOG operations
+			AddOperation(
+				"hypercog-stats",
+				shared.READ_OP,			
+				[]shared.MiddlewareInterface{
+					m2.RejectIfNotPublicAdmin,
+				},
+				nil,
+				stats.GetStatsOperation,
+			).
 			AddOperation(
 				"hypercog-list-bid-assets",
 				shared.READ_OP,			
@@ -206,7 +218,7 @@ func ContextOperations(m shared.AbstractChaincodeOperationManager) error {
 			AddOperationWithoutDefaults(
 				"hypercog-bid-asset",
 				shared.WRITE_OP,
-				&assetController,
+				&assetController, // Not used, just because it is required
 				bidController.StubReader,
 				bidController.LedgerReader,
 				[]shared.MiddlewareInterface{
@@ -218,7 +230,7 @@ func ContextOperations(m shared.AbstractChaincodeOperationManager) error {
 			AddOperationWithoutDefaults(
 				"hypercog-reject-asset",
 				shared.WRITE_OP,
-				&assetController,
+				&assetController, // Not used, just because it is required
 				bidDecisionController.StubReader,
 				bidDecisionController.LedgerReader,
 				[]shared.MiddlewareInterface{
@@ -230,7 +242,7 @@ func ContextOperations(m shared.AbstractChaincodeOperationManager) error {
 			AddOperationWithoutDefaults(
 				"hypercog-accept-asset",
 				shared.WRITE_OP,
-				&assetController,
+				&assetController, // Not used, just because it is required
 				bidDecisionController.StubReader,
 				bidDecisionController.LedgerReader,
 				[]shared.MiddlewareInterface{
diff --git a/chaincode/controller/stats/stats_controller.go b/chaincode/controller/stats/stats_controller.go
new file mode 100644
index 0000000..335d88d
--- /dev/null
+++ b/chaincode/controller/stats/stats_controller.go
@@ -0,0 +1,51 @@
+/**
+ * stats_controller.go
+ *
+ * COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
+ */
+
+package stats
+
+import (
+	"git.code.tecnalia.com/blockchain/hypercog/model"
+	"git.code.tecnalia.com/ledgerbuilder/sdk/core/api"
+	"git.code.tecnalia.com/ledgerbuilder/sdk/core/fabric/protos"
+	"git.code.tecnalia.com/ledgerbuilder/sdk/shared"
+)
+
+
+type StatsResult struct {
+	GlobalStats model.Stats `json:"global"` // in tons
+	PerCompany map[string]model.Stats `json:"perCompany,omitempty"`
+}
+
+func GetStatsOperation(stub shared.LedgerBuildrStubInterface, request shared.LedgerBuildrAsset) protos.Response {
+	fnName := "GetStats"
+
+	ret := new(StatsResult)
+
+	orgs, err := getOrgList(stub)
+	if err != nil {
+		return api.NewApiResponsePtr(fnName, err, nil).SendResponse()
+	}
+
+	globalStat, err := GetStats(stub, GLOBAL_KEY)
+	if err != nil {
+		return api.NewApiResponsePtr(fnName, err, nil).SendResponse()
+	}
+	ret.GlobalStats = *globalStat
+
+	if len(*orgs) > 0 {
+		ret.PerCompany = make(map[string]model.Stats)
+	}
+
+	for _, org := range *orgs {
+		orgStat, err := GetStats(stub, org)
+		if err != nil {
+			return api.NewApiResponsePtr(fnName, err, nil).SendResponse()
+		}
+		ret.PerCompany[org] = *orgStat
+    }
+
+	return api.NewAPIGenericResponsePtr(fnName, nil, ret).SendResponse()
+}
diff --git a/chaincode/controller/stats/stub.go b/chaincode/controller/stats/stub.go
new file mode 100644
index 0000000..f3763d4
--- /dev/null
+++ b/chaincode/controller/stats/stub.go
@@ -0,0 +1,94 @@
+/**
+ * stub.go
+ *
+ * COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
+ */
+
+package stats
+
+import (
+	"encoding/json"
+
+	"git.code.tecnalia.com/blockchain/hypercog/model"
+	"git.code.tecnalia.com/ledgerbuilder/sdk/shared"
+)
+
+const GLOBAL_KEY = "global"
+const ORG_LIST_KEY = "org_list"
+
+type OrgList []string
+
+
+func GetStats(stub shared.LedgerBuildrStubInterface, key string) (st *model.Stats, err error) {
+	value, err := stub.GetState(key)
+	if err != nil || len(value) == 0 {
+		st = new(model.Stats)
+	} else {
+		err = json.Unmarshal(value, &st)
+		if err != nil  {
+			return nil, err
+		}
+	}
+
+	return st, nil
+}
+
+func PutStats(stub shared.LedgerBuildrStubInterface, key string, stats *model.Stats) (error) {
+	serialized, err := json.Marshal(stats)
+	if err != nil {
+		return err
+	}
+
+	err = stub.PutState(key, serialized)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func getOrgList(stub shared.LedgerBuildrStubInterface) (st *OrgList, err error) {
+	value, err := stub.GetState(ORG_LIST_KEY)
+	if err != nil || len(value) == 0 {
+		st = new(OrgList)
+	} else {
+		err = json.Unmarshal(value, &st)
+		if err != nil  {
+			return nil, err
+		}
+	}
+
+	return st, nil
+}
+
+func (orgs OrgList) contains(org string) (bool) {
+	for _, o := range orgs {
+        if o == org {
+            return true
+        }
+    }
+	return false
+}
+
+func AddOrgIfNotExist(stub shared.LedgerBuildrStubInterface, org string) (err error) {
+	orgs, err := getOrgList(stub)
+	if err != nil {
+		return err
+	}
+
+	if !orgs.contains(org) {
+		newOrgs := append(*orgs, org)
+
+		serialized, err := json.Marshal(newOrgs)
+		if err != nil {
+			return err
+		}
+	
+		err = stub.PutState(ORG_LIST_KEY, serialized)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
\ No newline at end of file
diff --git a/chaincode/main.go b/chaincode/main.go
index 9b6db41..a7bc19e 100644
--- a/chaincode/main.go
+++ b/chaincode/main.go
@@ -18,7 +18,7 @@ import (
 const (
 	defaultContractConfig = `{
 	"context_id": 0,
-	"chaincode_name": "traceblock-cc",
+	"chaincode_name": "hypercog-cc",
 	"features": {
 		"force_lowercase_ids": true,
 		"generate_abi": false,
diff --git a/chaincode/middleware/acl.go b/chaincode/middleware/acl.go
index 2c8898c..6101957 100644
--- a/chaincode/middleware/acl.go
+++ b/chaincode/middleware/acl.go
@@ -14,6 +14,7 @@ import (
 var (
 	RejectIfNotSidenor = shared.NewMiddlewareFunction("reject-not-sidenor", rejectIfNotSidenor)
 	RejectIfNotCementCompany = shared.NewMiddlewareFunction("reject-not-cement-company", rejectIfNotCementCompany)
+	RejectIfNotPublicAdmin = shared.NewMiddlewareFunction("reject-not-public-admin", rejectIfNotPublicAdmin)
 	errInvalidOrg = errors.New("user belongs to an invalid organization")
 )
 
@@ -34,5 +35,15 @@ func rejectIfNotCementCompany(stub shared.LedgerBuildrStubInterface, ctl shared.
 		return request, nil
 	}
 	
+	return nil, errInvalidOrg
+}
+
+func rejectIfNotPublicAdmin(stub shared.LedgerBuildrStubInterface, ctl shared.ControllerInterface, req shared.TXRequestInterface, request shared.LedgerBuildrAsset) (shared.LedgerBuildrAsset, error) {
+	mspId := stub.GetMSPId()
+
+	if (mspId == "public-administration-com") {
+		return request, nil
+	}
+	
 	return nil, errInvalidOrg
 }
\ No newline at end of file
diff --git a/chaincode/middleware/stats.go b/chaincode/middleware/stats.go
new file mode 100644
index 0000000..d012f69
--- /dev/null
+++ b/chaincode/middleware/stats.go
@@ -0,0 +1,92 @@
+/**
+ * type.go
+ *
+ * COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
+ */
+
+package middleware
+
+import (
+	"git.code.tecnalia.com/blockchain/hypercog/controller/stats"
+	"git.code.tecnalia.com/blockchain/hypercog/model"
+	"git.code.tecnalia.com/ledgerbuilder/sdk/shared"
+	"git.code.tecnalia.com/traceblock/sdk/middleware"
+)
+
+var (
+	UpdateStats = shared.NewMiddlewareFunction("update-stats-slag", _updateSlagProduction)
+)
+
+
+func _retrieveExistingStats(stub shared.LedgerBuildrStubInterface) (*model.Stats, *model.Stats, error) {
+	org, err := stub.GetOrganization()
+	if err != nil {
+		return nil, nil, err
+	}
+
+	customStats, err := stats.GetStats(stub, org)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	globalStats, err := stats.GetStats(stub , stats.GLOBAL_KEY)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return globalStats, customStats, nil
+}
+
+func _upgradeStats(stub shared.LedgerBuildrStubInterface, globalStats *model.Stats, customStats *model.Stats) (error) {
+	org, err := stub.GetOrganization()
+	if err != nil {
+		return err
+	}
+
+	err = stats.PutStats(stub, org, customStats)
+	if err != nil {
+		return err
+	}
+
+	err = stats.PutStats(stub, stats.GLOBAL_KEY, globalStats)
+	if err != nil {
+		return err
+	}
+
+	err = stats.AddOrgIfNotExist(stub, org)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func _updateSlagProduction(stub shared.LedgerBuildrStubInterface, ctl shared.ControllerInterface, req shared.TXRequestInterface, requestAsset shared.LedgerBuildrAsset) (shared.LedgerBuildrAsset, error) {
+	asst, err := middleware.ConvertToTraceableAsset(requestAsset)
+	if err != nil {
+		return nil, err
+	}
+
+	status, ok := asst.Get("status")
+	if ok && status == "slag" {
+		globalStats, customStats, err := _retrieveExistingStats(stub)
+		if err != nil {
+			return nil, err
+		}
+
+		amount := float64(asst.Quantity)
+		if asst.Units == "kg" {
+			amount /= 1000.0
+		}
+	
+		customStats.TotalSlag += amount
+		globalStats.TotalSlag += amount
+
+		err = _upgradeStats(stub, globalStats, customStats)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return asst, nil
+}
\ No newline at end of file
diff --git a/chaincode/model/stats.go b/chaincode/model/stats.go
new file mode 100644
index 0000000..d6d1f36
--- /dev/null
+++ b/chaincode/model/stats.go
@@ -0,0 +1,17 @@
+/**
+ * stats.go
+ *
+ * COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
+ */
+
+package model
+
+// Total or per company
+type Stats struct {
+	TotalSlag float64 `json:"total"` // in tons
+	SlagReused float64 `json:"reused"` // in tons
+	SlagDiscarded float64 `json:"discarded"` // in tons
+	TotalPrice float64 `json:"price"` // in €
+	MinPrice float64 `json:"minPrice"` // in €
+	MaxPrice float64 `json:"maxPrice"` // in €
+}
-- 
GitLab