summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--brand/brand.go39
-rw-r--r--brand/db_actions.go69
-rw-r--r--brand/router.go (renamed from brand/brand_router.go)13
-rw-r--r--client/client.go75
-rw-r--r--client/db_actions.go62
-rw-r--r--client/router.go (renamed from client/client_router.go)13
-rw-r--r--database/database.go66
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--invoice/db_actions.go119
-rw-r--r--invoice/invoice.go114
-rw-r--r--invoice/router.go (renamed from invoice/invoice_router.go)52
-rw-r--r--item/db_actions.go82
-rw-r--r--item/item.go75
-rw-r--r--item/router.go (renamed from item/item_router.go)13
-rw-r--r--main.go2
-rw-r--r--web/templates/invoice.html15
-rw-r--r--web/templates/partials/item.html3
-rw-r--r--web/templates/partials/item_list.html7
19 files changed, 790 insertions, 32 deletions
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 <vidhukant@vidhukant.xyz>
+
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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/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 <vidhukant@vidhukant.xyz>
+
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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/brand_router.go b/brand/router.go
index 5d9a163..75c4eb4 100644
--- a/brand/brand_router.go
+++ b/brand/router.go
@@ -20,7 +20,6 @@ package brand
import (
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson/primitive"
- "github.com/MikunoNaka/OpenBills-lib/brand"
"log"
"net/http"
)
@@ -31,7 +30,7 @@ func Routes(route *gin.Engine) {
{
b.GET("/all", func(ctx *gin.Context) {
// TODO: add functionality to filter results
- brands, err := brand.GetBrands(nil)
+ 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())
@@ -42,9 +41,9 @@ func Routes(route *gin.Engine) {
})
b.POST("/new", func(ctx *gin.Context) {
- var b brand.Brand
+ var b Brand
ctx.BindJSON(&b)
- _, err := brand.SaveBrand(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())
@@ -64,9 +63,9 @@ func Routes(route *gin.Engine) {
return
}
- var b brand.Brand
+ var b Brand
ctx.BindJSON(&b)
- err = brand.ModifyBrand(objectId, 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())
@@ -86,7 +85,7 @@ func Routes(route *gin.Engine) {
return
}
- err = brand.DeleteBrand(objectId)
+ 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())
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 <vidhukant@vidhukant.xyz>
+
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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/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 <vidhukant@vidhukant.xyz>
+
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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/client_router.go b/client/router.go
index 2fd14fc..46e339a 100644
--- a/client/client_router.go
+++ b/client/router.go
@@ -19,7 +19,6 @@ package client
import (
"github.com/gin-gonic/gin"
- "github.com/MikunoNaka/OpenBills-lib/client"
"log"
"net/http"
"go.mongodb.org/mongo-driver/bson/primitive"
@@ -30,7 +29,7 @@ func Routes(route *gin.Engine) {
{
c.GET("/all", func(ctx *gin.Context) {
// TODO: add functionality to filter results
- clients, err := client.GetClients(nil)
+ 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())
@@ -41,9 +40,9 @@ func Routes(route *gin.Engine) {
})
c.POST("/new", func(ctx *gin.Context) {
- var c client.Client
+ var c Client
ctx.BindJSON(&c)
- _, err := client.SaveClient(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())
@@ -63,9 +62,9 @@ func Routes(route *gin.Engine) {
return
}
- var c client.Client
+ var c Client
ctx.BindJSON(&c)
- err = client.ModifyClient(objectId, 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())
@@ -85,7 +84,7 @@ func Routes(route *gin.Engine) {
return
}
- err = client.DeleteClient(objectId)
+ 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())
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 <vidhukant@vidhukant.xyz>
+
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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 <vidhukant@vidhukant.xyz>
+
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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 <vidhukant@vidhukant.xyz>
+
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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/router.go
index 88c6308..4e33e18 100644
--- a/invoice/invoice_router.go
+++ b/invoice/router.go
@@ -19,10 +19,11 @@ package invoice
import (
"github.com/gin-gonic/gin"
- "github.com/MikunoNaka/OpenBills-lib/invoice"
"log"
"net/http"
+ "strconv"
"go.mongodb.org/mongo-driver/bson/primitive"
+ "go.mongodb.org/mongo-driver/bson"
)
func Routes(route *gin.Engine) {
@@ -30,7 +31,7 @@ func Routes(route *gin.Engine) {
{
i.GET("/all", func(ctx *gin.Context) {
// TODO: add functionality to filter results
- invoices, err := invoice.GetInvoices(nil)
+ 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())
@@ -40,6 +41,43 @@ func Routes(route *gin.Engine) {
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)
@@ -49,7 +87,7 @@ func Routes(route *gin.Engine) {
return
}
- err = invoice.DeleteInvoice(objectId)
+ 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())
@@ -65,7 +103,7 @@ func Routes(route *gin.Engine) {
{
transport.GET("/all", func(ctx *gin.Context) {
// TODO: add functionality to filter results
- transports, err := invoice.GetTransports(nil)
+ 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())
@@ -84,7 +122,7 @@ func Routes(route *gin.Engine) {
return
}
- err = invoice.DeleteTransport(objectId)
+ 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())
@@ -100,7 +138,7 @@ func Routes(route *gin.Engine) {
{
transporter.GET("/all", func(ctx *gin.Context) {
// TODO: add functionality to filter results
- transporters, err := invoice.GetTransporters(nil)
+ 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())
@@ -119,7 +157,7 @@ func Routes(route *gin.Engine) {
return
}
- err = invoice.DeleteTransporter(objectId)
+ 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())
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 <vidhukant@vidhukant.xyz>
+
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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 <vidhukant@vidhukant.xyz>
+
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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/router.go
index d86af68..299e943 100644
--- a/item/item_router.go
+++ b/item/router.go
@@ -19,7 +19,6 @@ package item
import (
"github.com/gin-gonic/gin"
- "github.com/MikunoNaka/OpenBills-lib/item"
"go.mongodb.org/mongo-driver/bson/primitive"
"log"
"net/http"
@@ -31,7 +30,7 @@ func Routes(route *gin.Engine) {
// TODO: add functionality to filter results
// /all returns all the saved items
i.GET("/all", func(ctx *gin.Context) {
- items, err := item.GetItems(nil)
+ 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())
@@ -42,9 +41,9 @@ func Routes(route *gin.Engine) {
})
i.POST("/new", func(ctx *gin.Context) {
- var i item.Item
+ var i Item
ctx.BindJSON(&i)
- _, err := item.SaveItem(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())
@@ -64,9 +63,9 @@ func Routes(route *gin.Engine) {
return
}
- var i item.Item
+ var i Item
ctx.BindJSON(&i)
- err = item.ModifyItem(objectId, 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())
@@ -86,7 +85,7 @@ func Routes(route *gin.Engine) {
return
}
- err = item.DeleteItem(objectId)
+ 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())
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 @@
+<!doctype html>
+<html class="no-js" lang="">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
+ <title>OpenBills Testing</title>
+ <meta name="description" content="">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
+ </head>
+ <body>
+ {{ template "partials/item_list.html" .}}
+ </body>
+</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" }}
+<div>{{ .Name }}</div>
+{{ 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" }}
+<div class="items">
+ {{ range .Items }}
+ {{ template "partials/item.html" .}}
+ {{ end }}
+</div>
+{{ end }}