diff --git a/cnpj.go b/cnpj.go new file mode 100644 index 0000000..99b627c --- /dev/null +++ b/cnpj.go @@ -0,0 +1,125 @@ +package brazil + +import ( + "math/rand" + "regexp" + "strconv" + "time" +) + +// CNPJ struct +type cnpj struct { + number cnpjNumber + valid bool +} + +func (c cnpj) Number(mask bool) string { + if c.valid && mask { + return string(c.number[:2]) + + "." + + string(c.number[2:5]) + + "." + + string(c.number[5:8]) + + "/" + + string(c.number[8:12]) + + "-" + + string(c.number[12:]) + } + return string(c.number) +} + +func ParseCNPJ(number string) (cnpj, error) { + number = regexp.MustCompile(`[^0-9]`).ReplaceAllString(number, "") + + if len(number) != 14 { + return cnpj{}, errIncorrectLenghtCNPJNumber + } + + cnpjNumber := cnpjNumber(number) + + if !cnpjNumber.hasValidFirstDigit() { + return cnpj{}, errInvalidCNPJFirstDigit + } + + if !cnpjNumber.hasValidSecondDigit() { + return cnpj{}, errInvalidCNPJSecondDigit + } + + return cnpj{ + number: cnpjNumber, + valid: true, + }, nil +} + +func RandomCNPJNumber(mask bool) string { + var multipliers = []int{6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2} + + source := rand.NewSource(time.Now().UnixNano()) + r := rand.New(source) + cnpjNumber := int(r.Int63n(899999999999) + 100000000000) + cnpjString := strconv.Itoa(cnpjNumber) + + // Calculate first digit + sum := 0 + for i := 0; i < 12; i++ { + number, _ := strconv.Atoi(string(cnpjString[i])) + sum += number * multipliers[i+1] + } + firstDigit := 0 + if sum%11 >= 2 { + firstDigit = 11 - sum%11 + } + + // Calculate second digit + sum = 0 + for i := 0; i < 12; i++ { + number, _ := strconv.Atoi(string(cnpjString[i])) + sum += number * multipliers[i] + } + sum += firstDigit * multipliers[12] + secondDigit := 0 + if sum%11 >= 2 { + secondDigit = 11 - sum%11 + } + + if mask { + return cnpjString[:2] + "." + cnpjString[2:5] + "." + cnpjString[5:8] + "/" + cnpjString[8:12] + "-" + strconv.Itoa(firstDigit) + strconv.Itoa(secondDigit) + } + return cnpjString + strconv.Itoa(firstDigit) + strconv.Itoa(secondDigit) +} + +type cnpjNumber string + +func (c cnpjNumber) hasValidFirstDigit() bool { + var ( + multipliers = []int{5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2} + sum int + ) + + for i := 0; i < 12; i++ { + cnpjDigit, _ := strconv.Atoi(string(c[i])) + sum += cnpjDigit * multipliers[i] + } + if sum%11 < 2 { + return string(c[12]) == strconv.Itoa(0) + } + + return string(c[12]) == strconv.Itoa(11-sum%11) +} + +func (c cnpjNumber) hasValidSecondDigit() bool { + var ( + multipliers = []int{6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2} + sum int + ) + + for i := 0; i < 13; i++ { + cnpjDigit, _ := strconv.Atoi(string(c[i])) + sum += cnpjDigit * multipliers[i] + } + + if sum%11 < 2 { + return string(c[13]) == strconv.Itoa(0) + } + return string(c[13]) == strconv.Itoa(11-sum%11) +} diff --git a/cnpj_test.go b/cnpj_test.go new file mode 100644 index 0000000..3b6f823 --- /dev/null +++ b/cnpj_test.go @@ -0,0 +1,108 @@ +package brazil_test + +import ( + "testing" + + . "flavioltonon/go-brazil" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestParseCNPJ(t *testing.T) { + Convey("Given a string named s", t, func() { + var s string + + Convey("If s is empty", func() { + s = "" + + Convey("And the function ParseCNPJ is called using it as an argument", func() { + cnpj, err := ParseCNPJ(s) + + Convey("It should return an error", func() { + So(err, ShouldNotEqual, nil) + + Convey("And the CNPJ struct number should be empty", func() { + So(cnpj.Number(false), ShouldEqual, "") + }) + }) + }) + }) + + Convey("If s is a CNPJ number with an invalid first digit", func() { + s = "11222333000171" + + Convey("And the function ParseCNPJ is called using it as an argument", func() { + cnpj, err := ParseCNPJ(s) + + Convey("It should return an error", func() { + So(err, ShouldNotEqual, nil) + + Convey("And the CNPJ struct number should be empty", func() { + So(cnpj.Number(false), ShouldEqual, "") + }) + }) + }) + }) + + Convey("If s is a CNPJ number with an invalid second digit", func() { + s = "11222333000182" + + Convey("And the function ParseCNPJ is called using it as an argument", func() { + cnpj, err := ParseCNPJ(s) + + Convey("It should return an error", func() { + So(err, ShouldNotEqual, nil) + + Convey("And the CNPJ struct number should be empty", func() { + So(cnpj.Number(false), ShouldEqual, "") + }) + }) + }) + }) + + Convey("If s is a valid CNPJ number", func() { + s = "11222333000181" + + Convey("And the function ParseCNPJ is called using it as an argument", func() { + cnpj, err := ParseCNPJ(s) + + Convey("It should not return an error", func() { + So(err, ShouldEqual, nil) + + Convey("And the CNPJ struct number should exist", func() { + So(cnpj.Number(false), ShouldEqual, "11222333000181") + So(cnpj.Number(true), ShouldEqual, "11.222.333/0001-81") + }) + }) + }) + }) + }) +} + +func TestRandomCNPJNumber(t *testing.T) { + Convey("Given the function RandomCNPJNumber", t, func() { + Convey("If its mask argument equals true", func() { + number := RandomCNPJNumber(true) + + Convey("It should return a valid CNPJ number", func() { + cnpj, err := ParseCNPJ(number) + + So(err, ShouldEqual, nil) + So(cnpj.Number(false), ShouldNotEqual, "") + So(cnpj.Number(true), ShouldNotEqual, "") + }) + }) + + Convey("If its mask argument equals false", func() { + number := RandomCNPJNumber(false) + + Convey("It should return a valid CNPJ number", func() { + cnpj, err := ParseCNPJ(number) + + So(err, ShouldEqual, nil) + So(cnpj.Number(false), ShouldNotEqual, "") + So(cnpj.Number(true), ShouldNotEqual, "") + }) + }) + }) +}