diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..410fd7ff513dca0c5a5965ac6bb7f35646b50ce0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +vendor + +main diff --git a/barcode/barcode.go b/barcode/barcode.go new file mode 100644 index 0000000000000000000000000000000000000000..e12c1cc2936669cf9f7475290f0971b8c1c6913d --- /dev/null +++ b/barcode/barcode.go @@ -0,0 +1,131 @@ +package barcode + +import ( + "strconv" + "strings" + "unicode" + + "github.com/homework-2-test-first-development/errorcode" + "github.com/homework-2-test-first-development/product" + "github.com/homework-2-test-first-development/validationresult" +) + +type BarcodeInfo struct { + Barcode string + Amount uint32 +} + +func NewBarcode(barcode string, amount uint32) *BarcodeInfo { + return &BarcodeInfo{ + Barcode: barcode, + Amount: amount, + } +} + +func ValidateBarcodes(selectedBarcodes []string, products []*product.Product) *validationresult.ValidationResult { + err := errorcode.NewErr("", errorcode.InvalidBarcode) + if selectedBarcodes == nil { + return validationresult.NewValidationResult("", err) + } + + for _, v := range selectedBarcodes { + barcode := strings.Split(v, "-") + if barcode == nil || len(barcode) > 2 { + return validationresult.NewValidationResult("", err) + } + + barcodeString := barcode[0] + if !strings.HasPrefix(barcodeString, "ITEM") { + return validationresult.NewValidationResult("", err) + } + + barcodeNum := barcodeString[4:] + if !IsAllNumber(barcodeNum) { // format: ITEM000008 position 4 + return validationresult.NewValidationResult("", err) + } + + if len(barcode) == 2 { + amount, amountErr := strconv.Atoi(barcode[1]) + if amountErr != nil { + return validationresult.NewValidationResult("", err) + } + + if amount <= 0 { + return validationresult.NewValidationResult("", err) + } + } + + if !IsBarcodeInProductList(barcodeString, products) { + err.Code = errorcode.ProductNotFound + return validationresult.NewValidationResult("", err) + } + } + + err.Code = errorcode.Valid + return validationresult.NewValidationResult("", err) +} + +func IsAllNumber(str string) bool { + for _, v := range str { + if unicode.IsNumber(v) == false { + return false + } + } + return true +} + +func IsBarcodeInProductList(barcode string, products []*product.Product) bool { + for _, v := range products { + if barcode == v.Barcode { + return true + } + } + + return false +} + +func ParseBarcode(selectedBarcodes []string) []*BarcodeInfo { + barcodesAndAmount := make([]*BarcodeInfo, 0) + + for _, v := range selectedBarcodes { + barcode := strings.Split(v, "-") + barcodeString := barcode[0] + + if len(barcode) == 1 { + barcodesAndAmount = append(barcodesAndAmount, NewBarcode(barcodeString, 1)) + } else { + amount, _ := strconv.Atoi(barcode[1]) + barcodesAndAmount = append(barcodesAndAmount, NewBarcode(barcodeString, uint32(amount))) + } + } + + return barcodesAndAmount +} + +func GroupBarcode(barcodeInfo []*BarcodeInfo) []*BarcodeInfo { + groupedBarcodes := make([]*BarcodeInfo, 0) + if barcodeInfo == nil || len(barcodeInfo) == 0 { + return groupedBarcodes + } + + for _, v := range barcodeInfo { + pos := IsContainBarcode(v, groupedBarcodes) + if pos != -1 { + groupedBarcodes[pos].Amount += v.Amount + continue + } + groupedBarcodes = append(groupedBarcodes, NewBarcode(v.Barcode, v.Amount)) + } + + return groupedBarcodes +} + +func IsContainBarcode(barcode *BarcodeInfo, barcodeInfo []*BarcodeInfo) int { + for k, v := range barcodeInfo { + if barcode.Barcode == v.Barcode { + return k + } + } + + return -1 +} diff --git a/barcode/barcode_test.go b/barcode/barcode_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2ccfcc9e8dc90c9dd62a39d17fd05961c5a13736 --- /dev/null +++ b/barcode/barcode_test.go @@ -0,0 +1,149 @@ +package barcode + +import ( + "testing" + + "github.com/homework-2-test-first-development/errorcode" + "github.com/homework-2-test-first-development/product" + "github.com/stretchr/testify/assert" +) + +func TestValidateBarcodesWithJustOneProduct(t *testing.T) { + // Given + selectedBarcodes := make([]string, 0) + selectedBarcodes = append(selectedBarcodes, "ITEM000001") + + products := make([]*product.Product, 0) + products = append(products, product.NewProduct("cola", 3.00, "ITEM000001")) + + // When + result := ValidateBarcodes(selectedBarcodes, products) + + // Then + assert.Equal(t, errorcode.Valid, result.ErrorMes.Code) +} + +func TestValidateBarcodesIfBarcodeIsInvalid(t *testing.T) { + // Given + selectedBarcodes := make([]string, 0) + selectedBarcodes = append(selectedBarcodes, "123456-100") + + products := make([]*product.Product, 0) + products = append(products, product.NewProduct("cola", 3.00, "ITEM000001")) + + // When + result := ValidateBarcodes(selectedBarcodes, products) + + // Then + assert.Equal(t, errorcode.InvalidBarcode, result.ErrorMes.Code) +} + +func TestValidateBarcodesIfBarcodeIsValid(t *testing.T) { + // Given + selectedBarcodes := make([]string, 0) + selectedBarcodes = append(selectedBarcodes, "ITEM000001-2") + selectedBarcodes = append(selectedBarcodes, "ITEM000002-2") + + products := make([]*product.Product, 0) + products = append(products, product.NewProduct("cola", 3.00, "ITEM000001")) + products = append(products, product.NewProduct("water", 1.00, "ITEM000002")) + + // When + result := ValidateBarcodes(selectedBarcodes, products) + + // Then + assert.Equal(t, errorcode.Valid, result.ErrorMes.Code) +} + +func TestValidateBarcodesIfBarcodeAmountIsInvalid(t *testing.T) { + // Given + selectedBarcodes := make([]string, 0) + selectedBarcodes = append(selectedBarcodes, "ITEM000001-0") + + products := make([]*product.Product, 0) + products = append(products, product.NewProduct("cola", 3.00, "ITEM000001")) + products = append(products, product.NewProduct("water", 1.00, "ITEM000002")) + + // When + result := ValidateBarcodes(selectedBarcodes, products) + + // Then + assert.Equal(t, errorcode.InvalidBarcode, result.ErrorMes.Code) +} + +func TestValidateBarcodesIfProductNotFound(t *testing.T) { + // Given + selectedBarcodes := make([]string, 0) + selectedBarcodes = append(selectedBarcodes, "ITEM000008-2") + selectedBarcodes = append(selectedBarcodes, "ITEM000009-3") + + products := make([]*product.Product, 0) + products = append(products, product.NewProduct("cola", 3.00, "ITEM000001")) + products = append(products, product.NewProduct("water", 1.00, "ITEM000002")) + + // When + result := ValidateBarcodes(selectedBarcodes, products) + + // Then + assert.Equal(t, errorcode.ProductNotFound, result.ErrorMes.Code) +} + +func TestParseBarcodeWithValidSelectedBarcodes(t *testing.T) { + // Given + selectedBarcodes := make([]string, 0) + selectedBarcodes = append(selectedBarcodes, "ITEM000001-2") + selectedBarcodes = append(selectedBarcodes, "ITEM000002-3") + selectedBarcodes = append(selectedBarcodes, "ITEM000003") + selectedBarcodes = append(selectedBarcodes, "ITEM000003") + selectedBarcodes = append(selectedBarcodes, "ITEM000004") + + barcodesAndAmount := make([]*BarcodeInfo, 0) + barcodesAndAmount = append(barcodesAndAmount, NewBarcode("ITEM000001", 2)) + barcodesAndAmount = append(barcodesAndAmount, NewBarcode("ITEM000002", 3)) + barcodesAndAmount = append(barcodesAndAmount, NewBarcode("ITEM000003", 1)) + barcodesAndAmount = append(barcodesAndAmount, NewBarcode("ITEM000003", 1)) + barcodesAndAmount = append(barcodesAndAmount, NewBarcode("ITEM000004", 1)) + + // When + result := ParseBarcode(selectedBarcodes) + + // Then + assert.Equal(t, barcodesAndAmount, result) +} + +func TestGroupBarcodeWithEmptyBarcodeAndAmount(t *testing.T) { + // Given + barcodesAndAmount := make([]*BarcodeInfo, 0) + groupedBarcodes := make([]*BarcodeInfo, 0) + + // When + result := GroupBarcode(barcodesAndAmount) + + // Then + assert.Equal(t, groupedBarcodes, result) +} + +func TestGroupBarcodeWithValidBarcodeAndAmount(t *testing.T) { + // Given + barcodesAndAmount := make([]*BarcodeInfo, 0) + barcodesAndAmount = append(barcodesAndAmount, NewBarcode("ITEM000001", 2)) + barcodesAndAmount = append(barcodesAndAmount, NewBarcode("ITEM000002", 3)) + barcodesAndAmount = append(barcodesAndAmount, NewBarcode("ITEM000003", 1)) + barcodesAndAmount = append(barcodesAndAmount, NewBarcode("ITEM000003", 1)) + barcodesAndAmount = append(barcodesAndAmount, NewBarcode("ITEM000004", 1)) + + groupedBarcodes := make([]*BarcodeInfo, 0) + groupedBarcodes = append(groupedBarcodes, NewBarcode("ITEM000001", 2)) + groupedBarcodes = append(groupedBarcodes, NewBarcode("ITEM000002", 3)) + groupedBarcodes = append(groupedBarcodes, NewBarcode("ITEM000003", 2)) + groupedBarcodes = append(groupedBarcodes, NewBarcode("ITEM000004", 1)) + + // When + result := GroupBarcode(barcodesAndAmount) + + // Then + assert.Equal(t, groupedBarcodes[0], result[0]) + assert.Equal(t, groupedBarcodes[1], result[1]) + assert.Equal(t, groupedBarcodes[2], result[2]) + assert.Equal(t, groupedBarcodes[3], result[3]) +} diff --git a/errorcode/error_code.go b/errorcode/error_code.go new file mode 100644 index 0000000000000000000000000000000000000000..b1c2bae3cff9154428c780847072d25ffe783696 --- /dev/null +++ b/errorcode/error_code.go @@ -0,0 +1,21 @@ +package errorcode + +import "fmt" + +const ( + Valid = 0 + InvalidBarcode = 1 + ProductNotFound = 2 +) + +type Err struct { + error + Code int +} + +func NewErr(msg string, code int) Err { + return Err{ + error: fmt.Errorf(msg), + Code: code, + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..f6a14de9843b61519d8b6299fbaa81a4fd92c39d --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/homework-2-test-first-development + +go 1.18 + +require github.com/stretchr/testify v1.8.1 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..2ec90f70f8ed393a50b02e03ebb808e4aa34e822 --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000000000000000000000000000000000000..c04811917f0218be3c10c48c5d26f129a82812f2 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("hello world") +} diff --git a/product/product.go b/product/product.go new file mode 100644 index 0000000000000000000000000000000000000000..041b6036a16c929a5883eb72d1ac32a078d72b89 --- /dev/null +++ b/product/product.go @@ -0,0 +1,15 @@ +package product + +type Product struct { + Name string + Price float32 + Barcode string +} + +func NewProduct(name string, price float32, barcode string) *Product { + return &Product{ + Name: name, + Price: price, + Barcode: barcode, + } +} diff --git a/product/product_test.go b/product/product_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e6ae091891214a9af6e909ae2bb651b6c4f161e8 --- /dev/null +++ b/product/product_test.go @@ -0,0 +1 @@ +package product diff --git a/receipt/receipt.go b/receipt/receipt.go new file mode 100644 index 0000000000000000000000000000000000000000..a2d3a63db162ff110a19dde6c61a166d67ae7f19 --- /dev/null +++ b/receipt/receipt.go @@ -0,0 +1,90 @@ +package receipt + +import ( + "fmt" + + "github.com/homework-2-test-first-development/barcode" + "github.com/homework-2-test-first-development/errorcode" + "github.com/homework-2-test-first-development/product" + "github.com/homework-2-test-first-development/validationresult" +) + +type ReceiptItem struct { + Name string + TotalPrice float32 + Amount uint32 +} + +type Receipt struct { + Products []*ReceiptItem + TotalPrice float32 +} + +func CreateReceiptItem(name string, totalPrice float32, amount uint32) *ReceiptItem { + return &ReceiptItem{ + Name: name, + TotalPrice: totalPrice, + Amount: amount, + } +} + +func CreateReceipt(groupedBarcodes []*barcode.BarcodeInfo, products []*product.Product) *Receipt { + receipt := &Receipt{} + receipt.Products = make([]*ReceiptItem, 0) + + // forrange groupedBarcodes + for _, v := range groupedBarcodes { + var name string + var price float32 + name, price = GetNameAndPriceFromProduct(v, products) + + receiptItem := &ReceiptItem{} + receiptItem.Name = name + receiptItem.TotalPrice = float32(v.Amount) * price + receiptItem.Amount = v.Amount + receipt.Products = append(receipt.Products, receiptItem) + + receipt.TotalPrice += receiptItem.TotalPrice + } + + return receipt +} + +func GetNameAndPriceFromProduct(barcodeInfo *barcode.BarcodeInfo, products []*product.Product) (string, float32) { + for _, v := range products { + if v.Barcode == barcodeInfo.Barcode { + return v.Name, v.Price + } + } + return "", 0 +} + +func (r *Receipt) CreateErrorReceipt(validationResult validationresult.ValidationResult) string { + if validationResult.ErrorMes.Code == errorcode.InvalidBarcode { + return "ERROR\n-------\nThe barcode cannot be recognized" + } + + if validationResult.ErrorMes.Code == errorcode.ProductNotFound { + return fmt.Sprintf("ERROR\\n-------\\nThe product cannot be found: %s", validationResult.Barcode) + } + + return "" +} + +func (r *Receipt) FormatReceipt(receipt *Receipt) string { + if receipt.Products == nil || len(receipt.Products) == 0 { + return "Receipt\n------\n------\nTotal: 0.00" + } + + itemStr := "Name: %s, Amount: %d, Total: %.2f\n" + result := "" + + for _, v := range receipt.Products { + item := fmt.Sprintf(itemStr, v.Name, v.Amount, v.TotalPrice) + result += item + } + + tailStr := fmt.Sprintf("-------\nTotal: %.2f", receipt.TotalPrice) + + return "Receipt\n-------\n" + result + tailStr +} diff --git a/receipt/receipt_test.go b/receipt/receipt_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7f84b1f7e2f4687fe6a8f5b59fe6d3bbef50b82e --- /dev/null +++ b/receipt/receipt_test.go @@ -0,0 +1,118 @@ +package receipt + +import ( + "testing" + + "github.com/homework-2-test-first-development/barcode" + "github.com/homework-2-test-first-development/errorcode" + "github.com/homework-2-test-first-development/product" + "github.com/homework-2-test-first-development/validationresult" + + "github.com/stretchr/testify/assert" +) + +func TestCreateReceiptWithEmptyGroupedBarcodes(t *testing.T) { + // Given + products := make([]*product.Product, 0) + products = append(products, product.NewProduct("cola", 3.00, "ITEM000001")) + products = append(products, product.NewProduct("water", 1.00, "ITEM000002")) + products = append(products, product.NewProduct("happywater", 5.00, "ITEM000003")) + products = append(products, product.NewProduct("beer", 3.00, "ITEM000004")) + + groupedBarcodes := make([]*barcode.BarcodeInfo, 0) + + receipt := Receipt{ + Products: make([]*ReceiptItem, 0), + TotalPrice: 0, + } + + // When + result := CreateReceipt(groupedBarcodes, products) + + // Then + assert.Equal(t, receipt, *result) +} + +func TestCreateReceiptWithValidGroupedBarcodes(t *testing.T) { + // Given + products := make([]*product.Product, 0) + products = append(products, product.NewProduct("cola", 3.00, "ITEM000001")) + products = append(products, product.NewProduct("water", 1.00, "ITEM000002")) + products = append(products, product.NewProduct("happywater", 5.00, "ITEM000003")) + products = append(products, product.NewProduct("beer", 3.00, "ITEM000004")) + + groupedBarcodes := make([]*barcode.BarcodeInfo, 0) + groupedBarcodes = append(groupedBarcodes, barcode.NewBarcode("ITEM000001", 4)) + groupedBarcodes = append(groupedBarcodes, barcode.NewBarcode("ITEM000002", 5)) + groupedBarcodes = append(groupedBarcodes, barcode.NewBarcode("ITEM000003", 1)) + groupedBarcodes = append(groupedBarcodes, barcode.NewBarcode("ITEM000004", 3)) + + receipt := &Receipt{ + Products: make([]*ReceiptItem, 0), + TotalPrice: 0, + } + receipt.Products = append(receipt.Products, CreateReceiptItem("cola", 12.00, 4)) + receipt.Products = append(receipt.Products, CreateReceiptItem("water", 5.00, 5)) + receipt.Products = append(receipt.Products, CreateReceiptItem("happywater", 5.00, 1)) + receipt.Products = append(receipt.Products, CreateReceiptItem("beer", 9.00, 3)) + receipt.TotalPrice = 31 + + // When + result := CreateReceipt(groupedBarcodes, products) + + // Then + assert.Equal(t, receipt.Products[0], result.Products[0]) + assert.Equal(t, receipt.Products[1], result.Products[1]) + assert.Equal(t, receipt.Products[2], result.Products[2]) + assert.Equal(t, receipt.Products[3], result.Products[3]) + assert.Equal(t, receipt.TotalPrice, result.TotalPrice) +} + +func TestCreateErrorReceiptWhenErrorTypeIsInvalidBarcode(t *testing.T) { + // Given + var validationResult validationresult.ValidationResult + validationResult.Barcode = "123456" + validationResult.ErrorMes = errorcode.NewErr("", errorcode.InvalidBarcode) + // When + receipt := Receipt{} + receiptContent := receipt.CreateErrorReceipt(validationResult) + // Then + assert.Equal(t, "ERROR\n-------\nThe barcode cannot be recognized", receiptContent) +} + +func TestCreateErrorReceiptWhenErrorTypeIsProductNotFound(t *testing.T) { + // Given + var validationResult validationresult.ValidationResult + validationResult.Barcode = "ITEM000008" + validationResult.ErrorMes = errorcode.NewErr("", errorcode.ProductNotFound) + // When + receipt := Receipt{} + receiptContent := receipt.CreateErrorReceipt(validationResult) + // Then + assert.Equal(t, "ERROR\\n-------\\nThe product cannot be found: ITEM000008", receiptContent) +} + +func TestFormatReceiptWithNoProducts(t *testing.T) { + // Given + receipt := &Receipt{} + // When + receiptContent := receipt.FormatReceipt(receipt) + // Then + assert.Equal(t, "Receipt\n------\n------\nTotal: 0.00", receiptContent) +} + +func TestFormatReceiptWithValidReceipt(t *testing.T) { + // Given + receipt := &Receipt{ + Products: make([]*ReceiptItem, 0), + TotalPrice: 0, + } + receipt.Products = append(receipt.Products, CreateReceiptItem("cola", 12.00, 4)) + receipt.Products = append(receipt.Products, CreateReceiptItem("water", 5.00, 5)) + receipt.TotalPrice = 17 + // When + receiptContent := receipt.FormatReceipt(receipt) + // Then + assert.Equal(t, "Receipt\n-------\nName: cola, Amount: 4, Total: 12.00\nName: water, Amount: 5, Total: 5.00\n-------\nTotal: 17.00", + receiptContent) +} diff --git a/validationresult/validation_result.go b/validationresult/validation_result.go new file mode 100644 index 0000000000000000000000000000000000000000..1726b52a096b0438732ff3eabcf7aac5886e9243 --- /dev/null +++ b/validationresult/validation_result.go @@ -0,0 +1,17 @@ +package validationresult + +import ( + "github.com/homework-2-test-first-development/errorcode" +) + +type ValidationResult struct { + Barcode string + ErrorMes errorcode.Err +} + +func NewValidationResult(barcode string, ErrorMes errorcode.Err) *ValidationResult { + return &ValidationResult{ + Barcode: barcode, + ErrorMes: ErrorMes, + } +}