From 2f4a92b0f1d02096427a2d1c97746bb52cdcc38a Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Fri, 11 Nov 2022 21:22:31 +0530 Subject: Merged OpenBills-lib code into OpenBills-server --- brand/brand.go | 39 ++++++++ brand/brand_router.go | 100 -------------------- brand/db_actions.go | 69 ++++++++++++++ brand/router.go | 99 ++++++++++++++++++++ client/client.go | 75 +++++++++++++++ client/client_router.go | 99 -------------------- client/db_actions.go | 62 ++++++++++++ client/router.go | 98 +++++++++++++++++++ database/database.go | 66 +++++++++++++ go.mod | 1 - go.sum | 2 - invoice/db_actions.go | 119 +++++++++++++++++++++++ invoice/invoice.go | 114 +++++++++++++++++++++++ invoice/invoice_router.go | 133 -------------------------- invoice/router.go | 171 ++++++++++++++++++++++++++++++++++ item/db_actions.go | 82 ++++++++++++++++ item/item.go | 75 +++++++++++++++ item/item_router.go | 100 -------------------- item/router.go | 99 ++++++++++++++++++++ main.go | 2 +- web/templates/invoice.html | 15 +++ web/templates/partials/item.html | 3 + web/templates/partials/item_list.html | 7 ++ 23 files changed, 1194 insertions(+), 436 deletions(-) create mode 100644 brand/brand.go delete mode 100644 brand/brand_router.go create mode 100644 brand/db_actions.go create mode 100644 brand/router.go create mode 100644 client/client.go delete mode 100644 client/client_router.go create mode 100644 client/db_actions.go create mode 100644 client/router.go create mode 100644 database/database.go create mode 100644 invoice/db_actions.go create mode 100644 invoice/invoice.go delete mode 100644 invoice/invoice_router.go create mode 100644 invoice/router.go create mode 100644 item/db_actions.go create mode 100644 item/item.go delete mode 100644 item/item_router.go create mode 100644 item/router.go create mode 100644 web/templates/invoice.html create mode 100644 web/templates/partials/item.html create mode 100644 web/templates/partials/item_list.html diff --git a/brand/brand.go b/brand/brand.go new file mode 100644 index 0000000..7aaf5e6 --- /dev/null +++ b/brand/brand.go @@ -0,0 +1,39 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package brand + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "github.com/MikunoNaka/OpenBills-server/database" +) + +// initialise a database connection for this package +// not sure if I should do this but I am... +var db *mongo.Collection = database.DB.Collection("Brands") + +/* An item may or may not be + * assigned to a brand + * + * brands can be used to group products + * to perform certain actions + */ +type Brand struct { + Id primitive.ObjectID `bson:"_id,omitempty" json:"Id"` + Name string `bson:"Name" json:"Name"` +} diff --git a/brand/brand_router.go b/brand/brand_router.go deleted file mode 100644 index 5d9a163..0000000 --- a/brand/brand_router.go +++ /dev/null @@ -1,100 +0,0 @@ -/* OpenBills-server - Server for libre billing software OpenBills-web - * Copyright (C) 2022 Vidhu Kant Sharma - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package brand - -import ( - "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson/primitive" - "github.com/MikunoNaka/OpenBills-lib/brand" - "log" - "net/http" -) - - -func Routes(route *gin.Engine) { - b := route.Group("/brand") - { - b.GET("/all", func(ctx *gin.Context) { - // TODO: add functionality to filter results - brands, err := brand.GetBrands(nil) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to read brands from DB: %v\n", err.Error()) - return - } - - ctx.JSON(http.StatusOK, brands) - }) - - b.POST("/new", func(ctx *gin.Context) { - var b brand.Brand - ctx.BindJSON(&b) - _, err := brand.SaveBrand(b) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to add new brand %v to DB: %v\n", b, err.Error()) - return - } - - log.Printf("Successfully saved new brand to DB: %v", b) - ctx.JSON(http.StatusOK, nil) - }) - - b.PUT("/:brandId", func(ctx *gin.Context) { - id := ctx.Param("brandId") - objectId, err := primitive.ObjectIDFromHex(id) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to modify brand, Error parsing ID: %v\n", err.Error()) - return - } - - var b brand.Brand - ctx.BindJSON(&b) - err = brand.ModifyBrand(objectId, b) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to modify brand %v: %v\n", objectId, err.Error()) - return - } - - log.Printf("Modified brand %v to %v.\n", objectId, b) - ctx.JSON(http.StatusOK, nil) - }) - - b.DELETE("/:brandId", func(ctx *gin.Context) { - id := ctx.Param("brandId") - objectId, err := primitive.ObjectIDFromHex(id) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete brand, Error parsing ID: %v\n", err.Error()) - return - } - - err = brand.DeleteBrand(objectId) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete brand %v: %v\n", objectId, err.Error()) - return - } - - log.Printf("Deleted brand %v from database.\n", objectId ) - ctx.JSON(http.StatusOK, nil) - }) - } -} diff --git a/brand/db_actions.go b/brand/db_actions.go new file mode 100644 index 0000000..eb5961c --- /dev/null +++ b/brand/db_actions.go @@ -0,0 +1,69 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package brand + +import ( + "context" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "github.com/MikunoNaka/OpenBills-server/database" +) + +var items *mongo.Collection = database.DB.Collection("Items") + +// Add brand to db +func saveBrand(b Brand) (primitive.ObjectID, error) { + res, err := db.InsertOne(context.TODO(), b) + return res.InsertedID.(primitive.ObjectID), err +} + +// Delete brand from DB +func deleteBrand(id primitive.ObjectID) error { + // delete brand + _, err := db.DeleteOne(context.TODO(), bson.M{"_id": id}) + if err != nil { + return err + } + + // delete items associated with this brand + _, err = items.DeleteMany(context.TODO(), bson.M{"Brand._id": id}) + return err +} + +// modify brand in DB +func modifyBrand(id primitive.ObjectID, nb Brand) error { + _, err := db.UpdateOne(context.TODO(), bson.D{{"_id", id}}, bson.D{{"$set", nb}}) + return err +} + +/* GetBrands queries the database and + * returns brands based on the given filter + * if filter is nil every brand is returned + */ +func getBrands(filter bson.M) ([]Brand, error) { + var brands []Brand + + cursor, err := db.Find(context.TODO(), filter) + if err != nil { + return brands, err + } + + err = cursor.All(context.TODO(), &brands) + return brands, err +} diff --git a/brand/router.go b/brand/router.go new file mode 100644 index 0000000..75c4eb4 --- /dev/null +++ b/brand/router.go @@ -0,0 +1,99 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package brand + +import ( + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson/primitive" + "log" + "net/http" +) + + +func Routes(route *gin.Engine) { + b := route.Group("/brand") + { + b.GET("/all", func(ctx *gin.Context) { + // TODO: add functionality to filter results + brands, err := getBrands(nil) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to read brands from DB: %v\n", err.Error()) + return + } + + ctx.JSON(http.StatusOK, brands) + }) + + b.POST("/new", func(ctx *gin.Context) { + var b Brand + ctx.BindJSON(&b) + _, err := saveBrand(b) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to add new brand %v to DB: %v\n", b, err.Error()) + return + } + + log.Printf("Successfully saved new brand to DB: %v", b) + ctx.JSON(http.StatusOK, nil) + }) + + b.PUT("/:brandId", func(ctx *gin.Context) { + id := ctx.Param("brandId") + objectId, err := primitive.ObjectIDFromHex(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to modify brand, Error parsing ID: %v\n", err.Error()) + return + } + + var b Brand + ctx.BindJSON(&b) + err = modifyBrand(objectId, b) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to modify brand %v: %v\n", objectId, err.Error()) + return + } + + log.Printf("Modified brand %v to %v.\n", objectId, b) + ctx.JSON(http.StatusOK, nil) + }) + + b.DELETE("/:brandId", func(ctx *gin.Context) { + id := ctx.Param("brandId") + objectId, err := primitive.ObjectIDFromHex(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete brand, Error parsing ID: %v\n", err.Error()) + return + } + + err = deleteBrand(objectId) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete brand %v: %v\n", objectId, err.Error()) + return + } + + log.Printf("Deleted brand %v from database.\n", objectId ) + ctx.JSON(http.StatusOK, nil) + }) + } +} diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..caa3076 --- /dev/null +++ b/client/client.go @@ -0,0 +1,75 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package client + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "github.com/MikunoNaka/OpenBills-server/database" +) + +// initialise a database connection for this package +// not sure if I should do this but I am... +var db *mongo.Collection = database.DB.Collection("Clients") + +/* each invoice has a client + * you should be able to: + * - add, modify, delete a client + * - add client to invoices + * - get all invoices associated with client, etc + */ + +/* each contact has one name + * but multiple contact addresses + * it is assumed that the first one + * has the highest priority + */ + +type Contact struct { + Name string `bson:"Name" json:"Name"` + Phones []string `bson:"Phones" json:"Phones"` + Emails []string `bson:"Emails" json:"Emails"` + Website string `bson:"Website" json:"Website"` +} + +type Address struct { + /* "Text" means the actual address lines. + * If address is 123, xyz colony, myCity, myState the Text + * will be 123, xyz colony, and + * State and City will be myCity and myState + * + * A multiline string is expected. + */ + Text string `bson:"Text" json:"Text"` + City string `bson:"City" json:"City"` + State string `bson:"State" json:"State"` + PostalCode string `bson:"PostalCode" json:"PostalCode"` + Country string `bson:"Country" json:"Country"` +} + +type Client struct { + Id primitive.ObjectID `bson:"_id,omitempty" json:"Id"` + Name string `bson:"Name" json:"Name"` + Contact Contact `bson:"Contact" json:"Contact"` + GSTIN string `bson:"GSTIN" json:"GSTIN"` + /* if shipping address is empty it means that + * the billing address is also shipping address + */ + BillingAddress Address `bson:"BillingAddress" json:"BillingAddress"` + ShippingAddresses []Address `bson:"ShippingAddresses,omitempty" json:"ShippingAddresses"` +} diff --git a/client/client_router.go b/client/client_router.go deleted file mode 100644 index 2fd14fc..0000000 --- a/client/client_router.go +++ /dev/null @@ -1,99 +0,0 @@ -/* OpenBills-server - Server for libre billing software OpenBills-web - * Copyright (C) 2022 Vidhu Kant Sharma - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package client - -import ( - "github.com/gin-gonic/gin" - "github.com/MikunoNaka/OpenBills-lib/client" - "log" - "net/http" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func Routes(route *gin.Engine) { - c := route.Group("/client") - { - c.GET("/all", func(ctx *gin.Context) { - // TODO: add functionality to filter results - clients, err := client.GetClients(nil) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to read clients from DB: %v\n", err.Error()) - return - } - - ctx.JSON(http.StatusOK, clients) - }) - - c.POST("/new", func(ctx *gin.Context) { - var c client.Client - ctx.BindJSON(&c) - _, err := client.SaveClient(c) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to add new client %v to DB: %v\n", c, err.Error()) - return - } - - log.Printf("Successfully saved new client to DB: %v", c) - ctx.JSON(http.StatusOK, nil) - }) - - c.PUT("/:clientId", func(ctx *gin.Context) { - id := ctx.Param("clientId") - objectId, err := primitive.ObjectIDFromHex(id) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to modify client, Error parsing ID: %v\n", err.Error()) - return - } - - var c client.Client - ctx.BindJSON(&c) - err = client.ModifyClient(objectId, c) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to modify client %v: %v\n", objectId, err.Error()) - return - } - - log.Printf("Modified client %v to %v.\n", objectId, c) - ctx.JSON(http.StatusOK, nil) - }) - - c.DELETE("/:clientId", func(ctx *gin.Context) { - id := ctx.Param("clientId") - objectId, err := primitive.ObjectIDFromHex(id) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete client, Error parsing ID: %v\n", err.Error()) - return - } - - err = client.DeleteClient(objectId) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete client %v: %v\n", objectId, err.Error()) - return - } - - log.Printf("Deleted client %v from database.\n", objectId ) - ctx.JSON(http.StatusOK, nil) - }) - } -} diff --git a/client/db_actions.go b/client/db_actions.go new file mode 100644 index 0000000..bf32d97 --- /dev/null +++ b/client/db_actions.go @@ -0,0 +1,62 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package client + +import ( + "context" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +/* TODO: Handle errors properly + * Send an API error response instead of log.Fatal + */ + +// Add client to db +func saveClient(c Client) (primitive.ObjectID, error) { + res, err := db.InsertOne(context.TODO(), c) + return res.InsertedID.(primitive.ObjectID), err +} + +// Delete client from DB +func deleteClient(id primitive.ObjectID) error { + _, err := db.DeleteOne(context.TODO(), bson.M{"_id": id}) + return err +} + +// modify client in DB +func modifyClient(id primitive.ObjectID, nc Client) error { + _, err := db.UpdateOne(context.TODO(), bson.D{{"_id", id}}, bson.D{{"$set", nc}}) + return err +} + +/* GetClients queries the database and + * returns clients based on the given filter + * if filter is nil every client is returned + */ +func getClients(filter bson.M) ([]Client, error) { + var clients []Client + + cursor, err := db.Find(context.TODO(), filter) + if err != nil { + return clients, err + } + + err = cursor.All(context.TODO(), &clients) + return clients, err +} diff --git a/client/router.go b/client/router.go new file mode 100644 index 0000000..46e339a --- /dev/null +++ b/client/router.go @@ -0,0 +1,98 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package client + +import ( + "github.com/gin-gonic/gin" + "log" + "net/http" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func Routes(route *gin.Engine) { + c := route.Group("/client") + { + c.GET("/all", func(ctx *gin.Context) { + // TODO: add functionality to filter results + clients, err := getClients(nil) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to read clients from DB: %v\n", err.Error()) + return + } + + ctx.JSON(http.StatusOK, clients) + }) + + c.POST("/new", func(ctx *gin.Context) { + var c Client + ctx.BindJSON(&c) + _, err := saveClient(c) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to add new client %v to DB: %v\n", c, err.Error()) + return + } + + log.Printf("Successfully saved new client to DB: %v", c) + ctx.JSON(http.StatusOK, nil) + }) + + c.PUT("/:clientId", func(ctx *gin.Context) { + id := ctx.Param("clientId") + objectId, err := primitive.ObjectIDFromHex(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to modify client, Error parsing ID: %v\n", err.Error()) + return + } + + var c Client + ctx.BindJSON(&c) + err = modifyClient(objectId, c) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to modify client %v: %v\n", objectId, err.Error()) + return + } + + log.Printf("Modified client %v to %v.\n", objectId, c) + ctx.JSON(http.StatusOK, nil) + }) + + c.DELETE("/:clientId", func(ctx *gin.Context) { + id := ctx.Param("clientId") + objectId, err := primitive.ObjectIDFromHex(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete client, Error parsing ID: %v\n", err.Error()) + return + } + + err = deleteClient(objectId) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete client %v: %v\n", objectId, err.Error()) + return + } + + log.Printf("Deleted client %v from database.\n", objectId ) + ctx.JSON(http.StatusOK, nil) + }) + } +} diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..a970ee1 --- /dev/null +++ b/database/database.go @@ -0,0 +1,66 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package database + +import ( + "context" + "log" + "time" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +/* This creates a new client and sets + * it to the value of MongoClient which + * can be imported by other packages + * + * I am not at all sure if this is the best + * actually no, even if this is a decent way to do it. + * But yea this seems to work + * (remember to close the connections!) + */ +var DB *mongo.Database +var client *mongo.Client + +func init() { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + var err error + client, err = mongo.Connect(ctx, options.Client().ApplyURI("mongodb://127.0.0.1:27017")) + if err != nil { + log.Fatal(err) + } + + log.Println("Successfully connected to MongoDB database.") + DB = client.Database("OpenBillsDB") +} + +func DisconnectDB() { + if client == nil { + return + } + + err := client.Disconnect(context.TODO()) + if err != nil { + log.Fatal(err) + } + + log.Println("Successfully closed connection with MongoDB.") +} diff --git a/go.mod b/go.mod index f40afc5..7573e09 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/MikunoNaka/OpenBills-server go 1.19 require ( - github.com/MikunoNaka/OpenBills-lib v1.1.1 github.com/gin-gonic/gin v1.8.1 go.mongodb.org/mongo-driver v1.10.2 ) diff --git a/go.sum b/go.sum index d319660..5a9a21b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/MikunoNaka/OpenBills-lib v1.1.1 h1:mIpvg7S4qMsJFXZ2mMP9CfEoUs+kNByyQhGqI7IkLok= -github.com/MikunoNaka/OpenBills-lib v1.1.1/go.mod h1:uAM49uISC12jAgqstIgehBzSd9QBKUZDde6INRvLyGU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= diff --git a/invoice/db_actions.go b/invoice/db_actions.go new file mode 100644 index 0000000..a4880fa --- /dev/null +++ b/invoice/db_actions.go @@ -0,0 +1,119 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package invoice + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +// add invoice to db +func saveInvoice(i Invoice) (primitive.ObjectID, error) { + res, err := db.Collection("Invoices").InsertOne(context.TODO(), i) + return res.InsertedID.(primitive.ObjectID), err +} + +// add transporter to db +func saveTransporter(t Transporter) (primitive.ObjectID, error) { + res, err := db.Collection("Transporters").InsertOne(context.TODO(), t) + return res.InsertedID.(primitive.ObjectID), err +} + +// add transport vehicle to db +func saveTransport(t *Transport) (primitive.ObjectID, error) { + res, err := db.Collection("Transports").InsertOne(context.TODO(), t) + return res.InsertedID.(primitive.ObjectID), err +} + +// Delete invoice from DB +func deleteInvoice(id primitive.ObjectID) error { + _, err := db.Collection("Invoices").DeleteOne(context.TODO(), bson.M{"_id": id}) + return err +} + +// Delete transporter from DB +func deleteTransporter(id primitive.ObjectID) error { + _, err := db.Collection("Transporters").DeleteOne(context.TODO(), bson.M{"_id": id}) + return err +} + +// Delete transport vehicle from DB +func deleteTransport(id primitive.ObjectID) error { + _, err := db.Collection("Transports").DeleteOne(context.TODO(), bson.M{"_id": id}) + return err +} + +// modify invoice in DB +func modifyInvoice(id primitive.ObjectID, ni Invoice) error { + _, err := db.Collection("Invoices").UpdateOne(context.TODO(), bson.D{{"_id", id}}, bson.D{{"$set", ni}}) + return err +} + +// modify transporter in DB +func modifyTransporter(id primitive.ObjectID, nt Transporter) error { + _, err := db.Collection("Transporters").UpdateOne(context.TODO(), bson.D{{"_id", id}}, bson.D{{"$set", nt}}) + return err +} + +// modify transport in DB +func modifyTransport(id primitive.ObjectID, nt Transport) error { + _, err := db.Collection("Transports").UpdateOne(context.TODO(), bson.D{{"_id", id}}, bson.D{{"$set", nt}}) + return err +} + +/* GetInvoices queries the database and + * returns invoices based on the given filter + * if filter is nil every invoice is returned + */ +func getInvoices(filter bson.M) ([]Invoice, error) { + var invoices []Invoice + + cursor, err := db.Collection("Invoices").Find(context.TODO(), filter) + if err != nil { + return invoices, err + } + + err = cursor.All(context.TODO(), &invoices) + return invoices, err +} + +func getTransporters(filter bson.M) ([]Transporter, error) { + var transporters []Transporter + + cursor, err := db.Collection("Transporters").Find(context.TODO(), filter) + if err != nil { + return transporters, err + } + + err = cursor.All(context.TODO(), &transporters) + return transporters, err +} + +func getTransports(filter bson.M) ([]Transport, error) { + var transports []Transport + + cursor, err := db.Collection("Transports").Find(context.TODO(), filter) + if err != nil { + return transports, err + } + + err = cursor.All(context.TODO(), &transports) + return transports, err +} diff --git a/invoice/invoice.go b/invoice/invoice.go new file mode 100644 index 0000000..d195ea3 --- /dev/null +++ b/invoice/invoice.go @@ -0,0 +1,114 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package invoice + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "github.com/MikunoNaka/OpenBills-server/client" + "github.com/MikunoNaka/OpenBills-server/item" + "github.com/MikunoNaka/OpenBills-server/database" + "time" +) + +// initialise a database connection for this package +// not sure if I should do this but I am... +var db *mongo.Database = database.DB + +/* you should be able to: + * - add, modify, delete an invoice + * - add client to invoice + * - add items to invoice + */ + +/* Transporter details can be stored in + * the DB. That is decided by the frontend. + * You can optionally store Transporter + * and Transport details which are often used + */ +type Transporter struct { + Id primitive.ObjectID `bson:"_id,omitempty" json:"Id"` + Name string `bson:"Name" json:"Name"` + GSTIN string `bson:"GSTIN" json:"GSTIN"` + // Issued ID for the transporter if any + TransporterId string `bson:"TransporterId,omitempty" json:"TransporterId"` +} + +// transport vehicle details +type Transport struct { + Id primitive.ObjectID `bson:"_id,omitempty" json:"Id"` + Transporter Transporter `bson:"Transporter,omitempty" json:"Transporter"` + VehicleNum string `bson:"VehicleNum" json:"VehicleNum"` + Note string `bson:"Note" json:"Note"` + TransportMethod string `bson:"TransportMethod" json:"TransportMethod"` +} + +/* The *legendary* Invoice struct + * Each Recipient, Item in invoice, Address + * every detail that can change in the future is + * saved in the database so even if values change + * the invoice will have the old details + * + * The _id of the items/recipients will also be stored + * so user can look at the new values of those fields + * if needed. This system is better because if + * item is deleted from the Db it won't mess + * up the invoice collection + * + * Things like IGST, CGST, Discount, Quantity, etc + * should be calculated on runtime. + * + * usually an invoice would store the currency + * for payment. OpenBills does NOT support + * international billing. The Db will hold the config + * for the default currency, etc. + */ +// TODO: add place of supply +type Invoice struct { + Id primitive.ObjectID `bson:"_id,omitempty" json:"Id"` // not the same as invoice number + InvoiceNumber int `bson:"InvoiceNumber" json:"InvoiceNumber"` + CreatedAt time.Time `bson:"CreatedAt" json:"CreatedAt"` + LastUpdated time.Time `bson:"LastUpdated,omitempty" json:"LastUpdated"` + Recipient client.Client `bson:"Recipient" json:"Recipient"` + Paid bool `bson:"Paid" json:"Paid"` + TransactionId string `bson:"TransactionId" json:"TransactionId"` + Transport Transport `bson:"Transport" json:"Transport"` + // user can apply a discount on the whole invoice + // TODO: float64 isn't the best for this + DiscountPercentage float64 `bson:"DiscountPercentage" json:"DiscountPercentage"` + /* client may have multiple shipping + * addresses but invoice only has one. + * Empty ShippingAddress means shipping + * address same as billing address + */ + BillingAddress client.Address `bson:"BillingAddress" json:"BillingAddress"` + ShippingAddress client.Address `bson:"ShippingAddress,omitempty" json:"ShippingAddress"` + Items []item.InvoiceItem `bson:"Items" json:"Items"` + // user can attach notes to the invoice + // frontend decides if recipient sees this or not + Note string `bson:"Note" json:"Note"` + + /* Invoices can be drafts + * I personally like this functionality + * because we can constantly save the + * invoice to the DB as a draft + * and if OpenBills crashes or is disconnected + * we still have the progress + */ + Draft bool `bson:"Draft" json:"Draft"` +} diff --git a/invoice/invoice_router.go b/invoice/invoice_router.go deleted file mode 100644 index 88c6308..0000000 --- a/invoice/invoice_router.go +++ /dev/null @@ -1,133 +0,0 @@ -/* OpenBills-server - Server for libre billing software OpenBills-web - * Copyright (C) 2022 Vidhu Kant Sharma - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package invoice - -import ( - "github.com/gin-gonic/gin" - "github.com/MikunoNaka/OpenBills-lib/invoice" - "log" - "net/http" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func Routes(route *gin.Engine) { - i := route.Group("/invoice") - { - i.GET("/all", func(ctx *gin.Context) { - // TODO: add functionality to filter results - invoices, err := invoice.GetInvoices(nil) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to read invoices from DB: %v\n", err.Error()) - return - } - - ctx.JSON(http.StatusOK, invoices) - }) - - i.DELETE("/:invoiceId", func(ctx *gin.Context) { - id := ctx.Param("invoiceId") - objectId, err := primitive.ObjectIDFromHex(id) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete invoice, Error parsing ID: %v\n", err.Error()) - return - } - - err = invoice.DeleteInvoice(objectId) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete invoice %v: %v\n", objectId, err.Error()) - return - } - - log.Printf("Deleted invoice %v from database.\n", objectId ) - ctx.JSON(http.StatusOK, nil) - }) - } - - transport := route.Group("/transport") - { - transport.GET("/all", func(ctx *gin.Context) { - // TODO: add functionality to filter results - transports, err := invoice.GetTransports(nil) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to read transport vehicles from DB: %v\n", err.Error()) - return - } - - ctx.JSON(http.StatusOK, transports) - }) - - transport.DELETE("/:transportId", func(ctx *gin.Context) { - id := ctx.Param("transportId") - objectId, err := primitive.ObjectIDFromHex(id) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete transport vehicle, Error parsing ID: %v\n", err.Error()) - return - } - - err = invoice.DeleteTransport(objectId) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete transport vehicle %v: %v\n", objectId, err.Error()) - return - } - - log.Printf("Deleted transport vehicle %v from database.\n", objectId ) - ctx.JSON(http.StatusOK, nil) - }) - } - - transporter := route.Group("/transporter") - { - transporter.GET("/all", func(ctx *gin.Context) { - // TODO: add functionality to filter results - transporters, err := invoice.GetTransporters(nil) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to read transporters from DB: %v\n", err.Error()) - return - } - - ctx.JSON(http.StatusOK, transporters) - }) - - transporter.DELETE("/:transporterId", func(ctx *gin.Context) { - id := ctx.Param("transporterId") - objectId, err := primitive.ObjectIDFromHex(id) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete transporter, Error parsing ID: %v\n", err.Error()) - return - } - - err = invoice.DeleteTransporter(objectId) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete transporter %v: %v\n", objectId, err.Error()) - return - } - - log.Printf("Deleted transporter %v from database.\n", objectId ) - ctx.JSON(http.StatusOK, nil) - }) - } -} diff --git a/invoice/router.go b/invoice/router.go new file mode 100644 index 0000000..4e33e18 --- /dev/null +++ b/invoice/router.go @@ -0,0 +1,171 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package invoice + +import ( + "github.com/gin-gonic/gin" + "log" + "net/http" + "strconv" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/bson" +) + +func Routes(route *gin.Engine) { + i := route.Group("/invoice") + { + i.GET("/all", func(ctx *gin.Context) { + // TODO: add functionality to filter results + invoices, err := getInvoices(nil) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to read invoices from DB: %v\n", err.Error()) + return + } + + ctx.JSON(http.StatusOK, invoices) + }) + + // preview invoice + i.GET("/preview/:invoiceNumber", func(ctx *gin.Context) { + num := ctx.Param("invoiceNumber") + numInt, _ := strconv.Atoi(num) + + invoice, err := getInvoices(bson.M{"InvoiceNumber": numInt}) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to read invoice %v from DB: %v\n", numInt, err.Error()) + return + } + + if len(invoice) == 0 { + ctx.JSON(http.StatusNotFound, gin.H{"error": "no invoice with this invoice number"}) + log.Printf("WARN: No invoice with number %v found", numInt) + return + } + + ctx.HTML(http.StatusOK, "invoice.html", gin.H{ + "Invoice": invoice[0], + }) + }) + + i.POST("/new", func(ctx *gin.Context) { + var i Invoice + ctx.BindJSON(&i) + _, err := saveInvoice(i) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to add new invoice %v to DB: %v\n", i, err.Error()) + return + } + + log.Printf("Successfully created new Invoice: %v", i) + ctx.JSON(http.StatusOK, nil) + }) + + i.DELETE("/:invoiceId", func(ctx *gin.Context) { + id := ctx.Param("invoiceId") + objectId, err := primitive.ObjectIDFromHex(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete invoice, Error parsing ID: %v\n", err.Error()) + return + } + + err = deleteInvoice(objectId) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete invoice %v: %v\n", objectId, err.Error()) + return + } + + log.Printf("Deleted invoice %v from database.\n", objectId ) + ctx.JSON(http.StatusOK, nil) + }) + } + + transport := route.Group("/transport") + { + transport.GET("/all", func(ctx *gin.Context) { + // TODO: add functionality to filter results + transports, err := getTransports(nil) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to read transport vehicles from DB: %v\n", err.Error()) + return + } + + ctx.JSON(http.StatusOK, transports) + }) + + transport.DELETE("/:transportId", func(ctx *gin.Context) { + id := ctx.Param("transportId") + objectId, err := primitive.ObjectIDFromHex(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete transport vehicle, Error parsing ID: %v\n", err.Error()) + return + } + + err = deleteTransport(objectId) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete transport vehicle %v: %v\n", objectId, err.Error()) + return + } + + log.Printf("Deleted transport vehicle %v from database.\n", objectId ) + ctx.JSON(http.StatusOK, nil) + }) + } + + transporter := route.Group("/transporter") + { + transporter.GET("/all", func(ctx *gin.Context) { + // TODO: add functionality to filter results + transporters, err := getTransporters(nil) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to read transporters from DB: %v\n", err.Error()) + return + } + + ctx.JSON(http.StatusOK, transporters) + }) + + transporter.DELETE("/:transporterId", func(ctx *gin.Context) { + id := ctx.Param("transporterId") + objectId, err := primitive.ObjectIDFromHex(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete transporter, Error parsing ID: %v\n", err.Error()) + return + } + + err = deleteTransporter(objectId) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete transporter %v: %v\n", objectId, err.Error()) + return + } + + log.Printf("Deleted transporter %v from database.\n", objectId ) + ctx.JSON(http.StatusOK, nil) + }) + } +} diff --git a/item/db_actions.go b/item/db_actions.go new file mode 100644 index 0000000..36f8364 --- /dev/null +++ b/item/db_actions.go @@ -0,0 +1,82 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package item + +import ( + "context" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/MikunoNaka/OpenBills-server/database" + "github.com/MikunoNaka/OpenBills-server/brand" + "go.mongodb.org/mongo-driver/mongo" +) + +var brands *mongo.Collection = database.DB.Collection("Brands") + +// Add item to db +func saveItem(i Item) (primitive.ObjectID, error) { + res, err := db.InsertOne(context.TODO(), i) + return res.InsertedID.(primitive.ObjectID), err +} + +// Delete item from DB +func deleteItem(id primitive.ObjectID) error { + _, err := db.DeleteOne(context.TODO(), bson.M{"_id": id}) + return err +} + +// modify item in DB +func modifyItem(id primitive.ObjectID, ni Item) error { + _, err := db.UpdateOne(context.TODO(), bson.D{{"_id", id}}, bson.D{{"$set", ni}}) + return err +} + +/* GetItems queries the database and + * returns items based on the given filter + * if filter is nil every item is returned + */ +func getItems(filter bson.M) ([]Item, error) { + var items []Item + + cursor, err := db.Find(context.TODO(), filter) + if err != nil { + return items, err + } + + err = cursor.All(context.TODO(), &items) + if err != nil { + return items, err + } + + for id, i := range items { + // continue if item doesn't have a brand + if (i.Brand.Id == primitive.ObjectID{}) { + continue + } + + var b brand.Brand + + err := brands.FindOne(context.TODO(), bson.M{"_id": i.Brand.Id}).Decode(&b) + if err != nil { + return items, err + } + items[id].Brand = b + } + + return items, err +} diff --git a/item/item.go b/item/item.go new file mode 100644 index 0000000..12b7d03 --- /dev/null +++ b/item/item.go @@ -0,0 +1,75 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package item + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "github.com/MikunoNaka/OpenBills-server/database" + "github.com/MikunoNaka/OpenBills-server/brand" +) + +// initialise a database connection for this package +// not sure if I should do this but I am... +var db *mongo.Collection = database.DB.Collection("Items") + +/* each invoice must contain at least one item + * you should be able to: + * - add, modify, delete an item + * - add item to invoice + */ + +/* An item is any product + * or service that can be sold + * Items may have a max and min quanity + * and some default fields like GST and Unit Price + * that don't need to be entered manually + * + * the front-end may or may not implement + * the default fields + * + * Items can be assigned brands + * and certain actions can be performed + * on the products of a brand altogether + */ +type Item struct { + Id primitive.ObjectID `bson:"_id,omitempty" json:"Id"` + Brand brand.Brand `bson:"Brand,omitempty" json:"Brand"` + UnitOfMeasure string `bson:"UnitOfMeasure" json:"UnitOfMeasure"` + HasDecimalQuantity bool `bson:"HasDecimalQuantity, json:"HasDecimalQuantity"` + // just the defaults, can be overridden in an invoice + Name string `bson:"Name" json:"Name"` + Description string `bson:"Description" json:"Description"` + HSN string `bson:"HSN" json:"HSN"` + UnitPrice float64 `bson:"UnitPrice" json:"UnitPrice"` + // default tax percentage + GSTPercentage float64 `bson:"GSTPercentage" json:"GSTPercentage"` + MaxQuantity float64 `bson:"MaxQuantity" json:"MaxQuantity"` + MinQuantity float64 `bson:"MinQuantity" json:"MinQuantity"` +} + +// Item but with extra fields an invoice might require +type InvoiceItem struct { + Item + /* Each product must have a quantity + * but it is upto the backend to enforce that + */ + // TODO: float64 isn't ideal, find a better way + Quantity float64 `bson:"Quantitiy" json:"Quantity"` + DiscountPercentage float64 `bson:"DiscountPercentage,omitempty" json:"DiscountPercentage"` +} diff --git a/item/item_router.go b/item/item_router.go deleted file mode 100644 index d86af68..0000000 --- a/item/item_router.go +++ /dev/null @@ -1,100 +0,0 @@ -/* OpenBills-server - Server for libre billing software OpenBills-web - * Copyright (C) 2022 Vidhu Kant Sharma - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package item - -import ( - "github.com/gin-gonic/gin" - "github.com/MikunoNaka/OpenBills-lib/item" - "go.mongodb.org/mongo-driver/bson/primitive" - "log" - "net/http" -) - -func Routes(route *gin.Engine) { - i := route.Group("/item") - { - // TODO: add functionality to filter results - // /all returns all the saved items - i.GET("/all", func(ctx *gin.Context) { - items, err := item.GetItems(nil) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to read items from DB: %v\n", err.Error()) - return - } - - ctx.JSON(http.StatusOK, items) - }) - - i.POST("/new", func(ctx *gin.Context) { - var i item.Item - ctx.BindJSON(&i) - _, err := item.SaveItem(i) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to add new item %v to DB: %v\n", i, err.Error()) - return - } - - log.Printf("Successfully saved new item to DB: %v", i) - ctx.JSON(http.StatusOK, nil) - }) - - i.PUT("/:itemId", func(ctx *gin.Context) { - id := ctx.Param("itemId") - objectId, err := primitive.ObjectIDFromHex(id) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to modify item, Error parsing ID: %v\n", err.Error()) - return - } - - var i item.Item - ctx.BindJSON(&i) - err = item.ModifyItem(objectId, i) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to modify item %v: %v\n", objectId, err.Error()) - return - } - - log.Printf("Modified item %v to %v.\n", objectId, i) - ctx.JSON(http.StatusOK, nil) - }) - - i.DELETE("/:itemId", func(ctx *gin.Context) { - id := ctx.Param("itemId") - objectId, err := primitive.ObjectIDFromHex(id) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete item, Error parsing ID: %v\n", err.Error()) - return - } - - err = item.DeleteItem(objectId) - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - log.Printf("ERROR: Failed to delete item %v: %v\n", objectId, err.Error()) - return - } - - log.Printf("Deleted item %v from database.\n", objectId ) - ctx.JSON(http.StatusOK, nil) - }) - } -} diff --git a/item/router.go b/item/router.go new file mode 100644 index 0000000..299e943 --- /dev/null +++ b/item/router.go @@ -0,0 +1,99 @@ +/* OpenBills-server - Server for libre billing software OpenBills-web + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package item + +import ( + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson/primitive" + "log" + "net/http" +) + +func Routes(route *gin.Engine) { + i := route.Group("/item") + { + // TODO: add functionality to filter results + // /all returns all the saved items + i.GET("/all", func(ctx *gin.Context) { + items, err := getItems(nil) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to read items from DB: %v\n", err.Error()) + return + } + + ctx.JSON(http.StatusOK, items) + }) + + i.POST("/new", func(ctx *gin.Context) { + var i Item + ctx.BindJSON(&i) + _, err := saveItem(i) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to add new item %v to DB: %v\n", i, err.Error()) + return + } + + log.Printf("Successfully saved new item to DB: %v", i) + ctx.JSON(http.StatusOK, nil) + }) + + i.PUT("/:itemId", func(ctx *gin.Context) { + id := ctx.Param("itemId") + objectId, err := primitive.ObjectIDFromHex(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to modify item, Error parsing ID: %v\n", err.Error()) + return + } + + var i Item + ctx.BindJSON(&i) + err = modifyItem(objectId, i) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to modify item %v: %v\n", objectId, err.Error()) + return + } + + log.Printf("Modified item %v to %v.\n", objectId, i) + ctx.JSON(http.StatusOK, nil) + }) + + i.DELETE("/:itemId", func(ctx *gin.Context) { + id := ctx.Param("itemId") + objectId, err := primitive.ObjectIDFromHex(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete item, Error parsing ID: %v\n", err.Error()) + return + } + + err = deleteItem(objectId) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + log.Printf("ERROR: Failed to delete item %v: %v\n", objectId, err.Error()) + return + } + + log.Printf("Deleted item %v from database.\n", objectId ) + ctx.JSON(http.StatusOK, nil) + }) + } +} diff --git a/main.go b/main.go index d7a9cd3..76c09dc 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( "github.com/MikunoNaka/OpenBills-server/item" "github.com/MikunoNaka/OpenBills-server/client" "github.com/MikunoNaka/OpenBills-server/invoice" - "github.com/MikunoNaka/OpenBills-lib/database" + "github.com/MikunoNaka/OpenBills-server/database" "github.com/gin-gonic/gin" ) diff --git a/web/templates/invoice.html b/web/templates/invoice.html new file mode 100644 index 0000000..77825fe --- /dev/null +++ b/web/templates/invoice.html @@ -0,0 +1,15 @@ + + + + + + OpenBills Testing + + + + + + + {{ template "partials/item_list.html" .}} + + diff --git a/web/templates/partials/item.html b/web/templates/partials/item.html new file mode 100644 index 0000000..04eabba --- /dev/null +++ b/web/templates/partials/item.html @@ -0,0 +1,3 @@ +{{ define "partials/item.html" }} +
{{ .Name }}
+{{ end }} diff --git a/web/templates/partials/item_list.html b/web/templates/partials/item_list.html new file mode 100644 index 0000000..f12a34a --- /dev/null +++ b/web/templates/partials/item_list.html @@ -0,0 +1,7 @@ +{{ define "partials/item_list.html.tmpl" }} +
+ {{ range .Items }} + {{ template "partials/item.html" .}} + {{ end }} +
+{{ end }} -- cgit v1.2.3