aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/App.js2
-rw-r--r--src/classes/client.js4
-rw-r--r--src/classes/invoice.js49
-rw-r--r--src/components/editors/invoice-headers-editor.js52
-rw-r--r--src/components/tables/invoice-summary.js3
-rw-r--r--src/components/tables/invoice-table.js74
-rw-r--r--src/views/invoice/new.js44
-rw-r--r--src/views/manage/invoices.js62
8 files changed, 266 insertions, 24 deletions
diff --git a/src/App.js b/src/App.js
index 1938304..a7a2e5c 100644
--- a/src/App.js
+++ b/src/App.js
@@ -24,6 +24,7 @@ import ManagementPage from './views/manage/manage';
import ManageItemsPage from './views/manage/items';
import ManageClientsPage from './views/manage/clients';
import ManageBrandsPage from './views/manage/brands';
+import ManageInvoicesPage from './views/manage/invoices';
const App = () => {
return (
@@ -36,6 +37,7 @@ const App = () => {
<Route exact path="/manage/items" element={<ManageItemsPage/>}/>
<Route exact path="/manage/clients" element={<ManageClientsPage/>}/>
<Route exact path="/manage/brands" element={<ManageBrandsPage/>}/>
+ <Route exact path="/manage/invoices" element={<ManageInvoicesPage/>}/>
<Route exact path="/manage" element={<ManagementPage/>}/>
<Route path="*" element={<h1>404</h1>}/>
</Routes>
diff --git a/src/classes/client.js b/src/classes/client.js
index d05655d..baa2ed7 100644
--- a/src/classes/client.js
+++ b/src/classes/client.js
@@ -54,8 +54,8 @@ export class InvoiceClient extends Client {
}
}
-export const saveClient = (item, ok, fail) => {
- axios.post("/client/new", item)
+export const saveClient = (client, ok, fail) => {
+ axios.post("/client/new", client)
.then(res => ok(res))
.catch(err => fail(err))
}
diff --git a/src/classes/invoice.js b/src/classes/invoice.js
index 1032b47..066cbc3 100644
--- a/src/classes/invoice.js
+++ b/src/classes/invoice.js
@@ -15,7 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-//import axios from "axios";
+import { InvoiceItem } from "./item";
+import { Client, Address } from "./client";
+
+import axios from "axios";
export class Transporter {
constructor() {
@@ -35,3 +38,47 @@ export class Transport {
this.TransportMethod = "";
}
}
+
+export class Invoice {
+ constructor() {
+ this.Id = null;
+ this.InvoiceNumber = 0;
+ this.TotalAmount = 0.00;
+ this.CreatedAt = new Date();
+ this.LastUpdated = null;
+ this.Recipient = new Client();
+ this.Paid = false;
+ this.TransactionId = "";
+ this.Transport = new Transport();
+ this.DiscountPercentage = 0;
+ this.BillingAddress = new Address();
+ this.ShippingAddress = new Address();
+ this.Items = [];
+ this.Note = "";
+ this.Draft = true;
+ }
+}
+
+export const getAllInvoices = (ok, fail) => {
+ axios.get("/invoice/all")
+ .then(res => ok(res.data))
+ .catch(err => fail())
+}
+
+export const saveInvoice = (invoice, ok, fail) => {
+ axios.post("/invoice/new", invoice)
+ .then(res => ok(res))
+ .catch(err => fail(err))
+}
+
+export const deleteInvoice = (id, ok, fail) => {
+ axios.delete(`/invoice/${id}`)
+ .then(res => ok())
+ .catch((err) => fail())
+}
+
+export const editInvoice = (item, ok, fail) => {
+ axios.put(`/invoice/${item.Id}`, item)
+ .then(res => ok())
+ .catch(err => fail());
+}
diff --git a/src/components/editors/invoice-headers-editor.js b/src/components/editors/invoice-headers-editor.js
index 4ab2dcc..d099e59 100644
--- a/src/components/editors/invoice-headers-editor.js
+++ b/src/components/editors/invoice-headers-editor.js
@@ -19,21 +19,25 @@ import './scss/invoice-headers.scss';
import { useState, useEffect } from 'react';
-const InvoiceHeadersEditor = ({roundOff, setRoundOff, transport, setTransport, transporter, setTransporter}) => {
- const handleTransportInput = e => {
+const InvoiceHeadersEditor = ({roundOff, setRoundOff, transport, setTransport}) => {
+ const handleInput = e => {
const { name, value } = e.target;
- setTransport(prev => ({
- ...prev,
- [name]: value
- }));
- }
- const handleTransporterInput = e => {
- const { name, value } = e.target;
- setTransporter(prev => ({
- ...prev,
- [name]: value
- }));
+ if (name.includes("Transporter.")) {
+ const n = name.split(".")[1];
+ const transporter = transport.Transporter;
+ transporter[n] = value;
+
+ setTransport(prev => ({
+ ...prev,
+ Transporter: transporter,
+ }));
+ } else {
+ setTransport(prev => ({
+ ...prev,
+ [name]: value
+ }));
+ }
}
return (
@@ -63,36 +67,54 @@ const InvoiceHeadersEditor = ({roundOff, setRoundOff, transport, setTransport, t
<label>
Vehicle Number:
<input
+ name="VehicleNum"
+ value={transport.VehicleNum}
+ onChange={handleInput}
type="text"/>
</label>
<label>
Transport Method:
<input
+ name="TransportMethod"
+ value={transport.TransportMethod}
+ onChange={handleInput}
type="text"/>
</label>
<label>
Transporter Name:
<input
+ name="Transporter.Name"
+ value={transport.Transporter.Name}
+ onChange={handleInput}
type="text"/>
</label>
<label>
Transporter GSTIN:
<input
+ name="Transporter.GSTIN"
+ value={transport.Transporter.GSTIN}
+ onChange={handleInput}
type="text"/>
</label>
<label>
Transporter ID:
<input
+ name="Transporter.TransporterId"
+ value={transport.Transporter.TransporterId}
+ onChange={handleInput}
type="text"/>
</label>
<label>
- Note:
- <textarea />
+ Delivery Note:
+ <textarea
+ name="Note"
+ value={transport.Note}
+ onChange={handleInput} />
</label>
</div>
</div>
diff --git a/src/components/tables/invoice-summary.js b/src/components/tables/invoice-summary.js
index a5c594f..75c2ddc 100644
--- a/src/components/tables/invoice-summary.js
+++ b/src/components/tables/invoice-summary.js
@@ -18,7 +18,7 @@
import './scss/summary.scss';
import { currency } from '../../classes/item';
-const InvoiceSummary = ({sum, roundOff}) => {
+const InvoiceSummary = ({sum, roundOff, submit}) => {
const totalRoundedOff = currency(sum.Amount !== undefined ? Math.round(sum.Amount.value) : 0.00);
const roundedOffDiff = sum.Amount !== undefined && roundOff ? sum.Amount.subtract(totalRoundedOff) : currency(0.00);
@@ -59,6 +59,7 @@ const InvoiceSummary = ({sum, roundOff}) => {
}
</tbody>
</table>
+ <button onClick={submit}>Generate Invoice</button>
</>
);
}
diff --git a/src/components/tables/invoice-table.js b/src/components/tables/invoice-table.js
new file mode 100644
index 0000000..3c37fe0
--- /dev/null
+++ b/src/components/tables/invoice-table.js
@@ -0,0 +1,74 @@
+/* OpenBills-web - Web based libre billing software
+ * 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/>.
+ */
+
+import './scss/table.scss';
+import { deleteInvoice } from './../../classes/invoice';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPencil, faTrashCan } from '@fortawesome/free-solid-svg-icons'
+
+const InvoiceTable = (props) => {
+ const handleEdit = (i) => {
+ props.setInvoiceToEdit(i)
+ }
+
+ const handleDelete = (i) => {
+ // TODO: add confirmation prompt
+ deleteInvoice(i.Id, handleDelSuccess, handleDelFail);
+ }
+
+ const handleDelSuccess = () => {
+ props.refresh();
+ }
+
+ const handleDelFail = () => {
+ alert("fail")
+ }
+
+ return (
+ <table className={"item-table"}>
+ <thead>
+ <tr>
+ <th>S. No</th>
+ <th>Invoice Number</th>
+ <th>Recipient Name</th>
+ <th>Date Created</th>
+ <th>Date Last Updated</th>
+ <th>Total Amount</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {props.items && props.items.map((i, id) => (
+ <tr key={id}>
+ <td>{id+1}</td>
+ <td>{i.InvoiceNumber}</td>
+ <td>{i.Recipient.Name}</td>
+ <td>{i.CreatedAt}</td>
+ <td>{i.UpdatedAt}</td>
+ <td>{i.TotalAmount}</td>
+ <td className={"buttons"}>
+ <FontAwesomeIcon icon={faPencil} onClick={() => handleEdit(i)}/>
+ <FontAwesomeIcon icon={faTrashCan} onClick={() => handleDelete(i)}/>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ );
+}
+
+export default InvoiceTable;
diff --git a/src/views/invoice/new.js b/src/views/invoice/new.js
index 717a772..2d88e40 100644
--- a/src/views/invoice/new.js
+++ b/src/views/invoice/new.js
@@ -23,8 +23,8 @@ import ItemTable from '../../components/tables/invoice-item-table';
import InvoiceSummary from '../../components/tables/invoice-summary';
import HeadersEditor from '../../components/editors/invoice-headers-editor';
-import { InvoiceClient } from '../../classes/client';
-import { Transport, Transporter } from '../../classes/invoice';
+import { InvoiceClient, Client } from '../../classes/client';
+import { Transport, Invoice, saveInvoice } from '../../classes/invoice';
import { calcSum, currency } from '../../classes/item';
import { useState, useEffect } from 'react';
@@ -35,6 +35,7 @@ const NewInvoicePage = () => {
const [items, setItems] = useState([]);
const [roundOffTotal, setRoundOffTotal] = useState(true); //TODO: load from config
//const [isInterstate, setIsInterstate] = useState(false);
+ const [transport, setTransport] = useState(new Transport());
const isInterstate = false; // temporary
const [sum, setSum] = useState({
GST: currency(0),
@@ -44,8 +45,38 @@ const NewInvoicePage = () => {
Quantity: currency(0)
});
- const [transporter, setTransporter] = useState(new Transporter());
- const [transport, setTransport] = useState(new Transport());
+ const submitInvoice = () => {
+ const invoice = new Invoice();
+ invoice.InvoiceNumber = 69; // TODO: set accordingly
+ invoice.CreatedAt = new Date();
+ invoice.TotalAmount = sum.Amount;
+
+ const recipient = new Client();
+ recipient.Name = client.Name;
+ recipient.Contact = client.Contact;
+ recipient.GSTIN = client.GSTIN;
+ recipient.BillingAddress = client.BillingAddress;
+ invoice.Recipient = recipient;
+
+ invoice.Paid = false; // TODO: set accordingly
+ invoice.TransactionId = "" // TODO: set accordingly
+ invoice.DiscountPercentage = 0;
+ invoice.BillingAddress = client.BillingAddress;
+ invoice.ShippingAddress = client.ShipTo;
+ invoice.Items = items;
+ invoice.Note = ""; // TODO: set accordingly
+ invoice.Draft = false; // TODO: set accordingly
+
+ saveInvoice(invoice, handleSuccess, handleFail)
+ }
+
+ const handleSuccess = () => {
+ alert("yay");
+ }
+
+ const handleFail = () => {
+ alert("fail");
+ }
useEffect(() => setShippingAddressId(-1), [client]);
@@ -69,10 +100,13 @@ const NewInvoicePage = () => {
<div className={"two-col"}>
<HeadersEditor
roundOff={roundOffTotal}
- setRoundOff={setRoundOffTotal} />
+ setRoundOff={setRoundOffTotal}
+ transport={transport}
+ setTransport={setTransport} />
<div>
<InvoiceSummary
sum={sum}
+ submit={submitInvoice}
roundOff={roundOffTotal} />
</div>
</div>
diff --git a/src/views/manage/invoices.js b/src/views/manage/invoices.js
new file mode 100644
index 0000000..375fbe5
--- /dev/null
+++ b/src/views/manage/invoices.js
@@ -0,0 +1,62 @@
+/* OpenBills-web - Web based libre billing software
+ * 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/>.
+ */
+
+/* This page shows a list of all the items
+ * with options to add/modify/delete items
+ */
+
+import { useState, useEffect } from 'react';
+
+import './scss/management-page.scss'
+import { Invoice, getAllInvoices } from '../../classes/invoice';
+import InvoiceTable from './../../components/tables/invoice-table';
+
+const ManageInvoicesPage = () => {
+ const [invoiceToEdit, setInvoiceToEdit] = useState(new Invoice());
+ const [allInvoices, setAllInvoices] = useState([]);
+
+ // TODO: handle error
+ const updateList = () =>
+ getAllInvoices(setAllInvoices, () => {});
+
+ useEffect(() => {
+ updateList();
+ }, []);
+
+ return (
+ <>
+ <InvoiceTable
+ refresh={updateList}
+ items={allInvoices}
+ setItemToEdit={setInvoiceToEdit}/>
+
+ {/*JSON.stringify(itemToEdit) !== JSON.stringify(new Item()) &&
+ <div className={"floating-wrapper"}>
+ <ItemEditor
+ className={"floating-window"}
+ heading={`Edit ${itemToEdit.Name ? itemToEdit.Name : 'Item'}:`}
+ item={itemToEdit}
+ hide={() => setItemToEdit(new Item())}
+ editing={true}
+ callback={updateList}/>
+ </div>
+ */}
+ </>
+ );
+}
+
+export default ManageInvoicesPage;