Skip to content

Commit 19e63f7

Browse files
committed
add reconcilation flow
1 parent fe5d04d commit 19e63f7

File tree

4 files changed

+303
-0
lines changed

4 files changed

+303
-0
lines changed

components/layout.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,16 @@ class Layout extends React.Component {
148148
</div>
149149
</li>
150150

151+
{this.state.loggedIn && this.state.admin && (
152+
153+
<li class="ml-4">
154+
<Link href="/admin/reconcile">
155+
<a> Reconcile </a>
156+
</Link>
157+
</li>
158+
159+
)}
160+
151161
</div>
152162
)}
153163
<li className="nav-items pointer capitalize">

controllers/v2/invoices.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {axios} from "../../DukaanAPI";
2+
import ErrorHandler from '../../helpers/ErrorHandler';
3+
4+
export const fetchInvoices = (options) => {
5+
return axios.get('/api/v2/admin/invoices', {params: options})
6+
.catch(err => {
7+
ErrorHandler.handle(err)
8+
})
9+
}
10+
11+
export const markReconciled = (data) => {
12+
return axios.post('/api/v2/admin/invoices/mark-reconciled', data)
13+
}
14+
15+
export const unmarkReconciled = (data) => {
16+
return axios.post('/api/v2/admin/invoices/unmark-reconciled', data)
17+
}

pages/admin/reconcile.jsx

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import { useState } from 'react';
2+
import Head from '../../components/head';
3+
import Layout from '../../components/layout';
4+
import DatePicker from 'react-datepicker';
5+
import { fetchInvoices, markReconciled, unmarkReconciled } from '../../controllers/v2/invoices'
6+
import moment from 'moment';
7+
import { useEffect } from 'react';
8+
import Table from '@material-ui/core/Table';
9+
import TableBody from '@material-ui/core/TableBody';
10+
import TableCell from '@material-ui/core/TableCell';
11+
import TableContainer from '@material-ui/core/TableContainer';
12+
import Grid from "@material-ui/core/Grid";
13+
import TableHead from '@material-ui/core/TableHead';
14+
import TableRow from '@material-ui/core/TableRow';
15+
import Paper from '@material-ui/core/Paper';
16+
import TablePagination from "@material-ui/core/TablePagination";
17+
import { withStyles } from '@material-ui/core';
18+
import {Modal, Box, Typography} from '@material-ui/core';
19+
import Button from '@material-ui/core/Button';
20+
import Dialog from "@material-ui/core/Dialog";
21+
import DialogContent from "@material-ui/core/DialogContent";
22+
import Swal from 'sweetalert2';
23+
24+
const PaginationTheme = withStyles({
25+
actions: {
26+
color: "red",
27+
backgroundColor: 'white',
28+
}
29+
})(TablePagination);
30+
31+
export default function Reconcile() {
32+
const [startDate, setStartDate] = useState(moment().toDate());
33+
const [endDate, setEndDate] = useState();
34+
const [comment, setComment] = useState('')
35+
const [showModal, setShowModal] = useState(false)
36+
const [offset, setOffset] = useState(0)
37+
const [limit, setLimit] = useState(25)
38+
const [invoices, setInvoices] = useState([])
39+
const [selectedInvoiceIds, setSelectedInvoiceIds] = useState([])
40+
const [paginationMeta, setPaginationMeta] = useState({currentPage: 0, count: 0})
41+
42+
useEffect(() => {
43+
async function fetchData() {
44+
const response = await fetchInvoices({ offset, limit })
45+
setInvoices(response.data.data)
46+
setPaginationMeta({ currentPage: response.data.pagesInfo.currentPage, count: response.data.pagesInfo.count})
47+
}
48+
fetchData()
49+
},[])
50+
51+
function handleChangePage(event, newPage) {
52+
setOffset(newPage * limit)
53+
}
54+
55+
function handleLimitChange(event) {
56+
setLimit(parseInt(event.target.value, 10))
57+
}
58+
59+
function handleModalClose(e) {
60+
console.log(e)
61+
}
62+
63+
function handleSelectRow(selected, invoice_id) {
64+
if(selected) {
65+
setSelectedInvoiceIds([...selectedInvoiceIds, invoice_id])
66+
} else {
67+
const selectedIds = [...selectedInvoiceIds]
68+
const index = selectedInvoiceIds.indexOf(invoice_id)
69+
selectedIds.splice(index, 1)
70+
setSelectedInvoiceIds(selectedIds)
71+
}
72+
}
73+
74+
async function handleMarkReconciled() {
75+
try {
76+
await markReconciled({ invoiceIds: selectedInvoiceIds, comment })
77+
setShowModal(false)
78+
Swal.fire({
79+
title: "Invoices reconciled successfully!",
80+
type: "success",
81+
showConfirmButton: true
82+
})
83+
84+
const response = await fetchInvoices({ offset, limit })
85+
setInvoices(response.data.data)
86+
setPaginationMeta({ currentPage: response.data.pagesInfo.currentPage, count: response.data.pagesInfo.count})
87+
88+
} catch(err) {
89+
Swal.fire({
90+
title: "Invoices Reconcilation Failed!",
91+
type: "error",
92+
showConfirmButton: true
93+
})
94+
}
95+
}
96+
97+
async function handleUnmarkReconciled() {
98+
try {
99+
await unmarkReconciled({ invoiceIds: selectedInvoiceIds })
100+
Swal.fire({
101+
title: "Invoices reconciled successfully!",
102+
type: "success",
103+
showConfirmButton: true
104+
})
105+
106+
const response = await fetchInvoices({ offset, limit })
107+
setInvoices(response.data.data)
108+
setPaginationMeta({ currentPage: response.data.pagesInfo.currentPage, count: response.data.pagesInfo.count})
109+
} catch(err) {
110+
Swal.fire({
111+
title: "Invoices Un-Reconcilation Failed!",
112+
type: "error",
113+
showConfirmButton: true
114+
})
115+
}
116+
}
117+
118+
useEffect(() => {
119+
async function fetchData() {
120+
const response = await fetchInvoices({ offset, limit })
121+
setInvoices(response.data.data)
122+
setPaginationMeta({ currentPage: response.data.pagesInfo.currentPage, count: response.data.pagesInfo.count})
123+
}
124+
fetchData()
125+
}, [limit, offset])
126+
127+
return (
128+
<div>
129+
<Head title="Coding Blocks | Dukaan | Coupon"/>
130+
<Layout/>
131+
<div className="my-4">
132+
<h3 className="t-align-c">Reconciler</h3>
133+
134+
{/* <div className="my-3">
135+
<span>Select Date Range</span>
136+
</div>
137+
<DatePicker
138+
className="border"
139+
selectsRange
140+
onChange={(update) => {
141+
console.log(update)
142+
}}
143+
isClearable={true}
144+
/> */}
145+
146+
<div className="my-4 d-flex justify-content-center">
147+
<Grid xs={11} className={"mt-5 mr-5"}>
148+
<Paper>
149+
<TableContainer>
150+
<Grid container justify="center" className={"mb-1"}>
151+
<div className="d-flex justify-content-between w-100 p-4">
152+
<h2 className={"title"}>Invoices</h2>
153+
<li className="dropdown mt-1 float-right">
154+
<button className="dropbtn dropdown-toggle">
155+
<span className="font-md">Actions</span>
156+
<i className="fa fa-caret-down pl-2" />
157+
</button>
158+
<div className="dropdown-content">
159+
<div className="flex-row justify-content-center">
160+
<button className="p-2" disabled={!!!selectedInvoiceIds.length} onClick={() => setShowModal(true)}>Mark Reconciled</button>
161+
</div>
162+
<div className="divider-h" />
163+
<div className="flex-row justify-content-center">
164+
<button className="p-2" disabled={!!!selectedInvoiceIds.length} onClick={() => handleUnmarkReconciled()}>Mark Un-Reconcile</button>
165+
</div>
166+
</div>
167+
</li>
168+
</div>
169+
</Grid>
170+
<Table aria-label="simple table">
171+
<TableHead>
172+
<TableRow>
173+
<TableCell align="center" className={"red"}>Select</TableCell>
174+
<TableCell align="center" className={"red"}>Name</TableCell>
175+
<TableCell align="center" className={"red"}>Email</TableCell>
176+
<TableCell align="center" className={"red"}>Product</TableCell>
177+
<TableCell align="center" className={"red"}>List Price</TableCell>
178+
<TableCell align="center" className={"red"}>Final Price</TableCell>
179+
<TableCell align="center" className={"red"}>CGST</TableCell>
180+
<TableCell align="center" className={"red"}>IGST</TableCell>
181+
<TableCell align="center" className={"red"}>SGST</TableCell>
182+
<TableCell align="center" className={"red"}>Tax</TableCell>
183+
<TableCell align="center" className={"red"}>Final Amount</TableCell>
184+
<TableCell align="center" className={"red"}>RazorPay Order Id</TableCell>
185+
<TableCell align="center" className={"red"}>RazorPay Payment Id</TableCell>
186+
<TableCell align="center" className={"red"}>Status</TableCell>
187+
<TableCell align="center" className={"red"}>Invoice Link</TableCell>
188+
<TableCell align="center" className={"red"}>Reconciled By</TableCell>
189+
</TableRow>
190+
</TableHead>
191+
<TableBody>
192+
{invoices.map(invoice => (
193+
<TableRow key={invoice.id}
194+
className={invoice.reconciled_by ? 'bg-green' : selectedInvoiceIds.includes(invoice.id) ? 'bg-blue' : ''}>
195+
<TableCell>
196+
<input type="checkbox" onChange={(e) => handleSelectRow(e.target.checked, invoice.id)} />
197+
</TableCell>
198+
<TableCell>{invoice.cart.buyer.firstname} {invoice.cart.buyer.lastname}</TableCell>
199+
<TableCell>{invoice.cart.buyer.email}</TableCell>
200+
<TableCell>{invoice.product.name}</TableCell>
201+
<TableCell>{invoice.list_price / 100}</TableCell>
202+
<TableCell>{invoice.final_price / 100}</TableCell>
203+
<TableCell>{invoice.cgst / 100}</TableCell>
204+
<TableCell>{invoice.igst / 100}</TableCell>
205+
<TableCell>{invoice.sgst / 100}</TableCell>
206+
<TableCell>{invoice.cart.total_tax_collected / 100}</TableCell>
207+
<TableCell>{invoice.amount / 100}</TableCell>
208+
<TableCell>{invoice.transaction?.razorpay?.order_id || invoice.transaction?.razorpay_order_id}</TableCell>
209+
<TableCell>{invoice.transaction?.razorpay?.payment_id || invoice.transaction?.razorpay_payment_id}</TableCell>
210+
<TableCell><span className={
211+
invoice.transaction.status === 'captured' ? 'green' : invoice.transaction.status === 'cancelled' ? 'red': 'yellow'
212+
}>{invoice.transaction?.status}</span></TableCell>
213+
<TableCell><a href={invoice.invoice_link} target="_blank">{invoice.invoice_link ? 'Link' : 'N/A'}</a></TableCell>
214+
<TableCell>{invoice.reconciledBy ? `${invoice.reconciledBy.firstname} ${invoice.reconciledBy.lastname}(${invoice.reconciledBy.oneauth_id}`: 'N/A' }</TableCell>
215+
</TableRow>
216+
))}
217+
</TableBody>
218+
</Table>
219+
</TableContainer>
220+
<PaginationTheme
221+
component="div"
222+
count={paginationMeta.count}
223+
rowsPerPage={limit}
224+
page={paginationMeta.currentPage}
225+
onChangePage={handleChangePage}
226+
onChangeRowsPerPage={handleLimitChange}
227+
/>
228+
</Paper>
229+
</Grid>
230+
</div>
231+
</div>
232+
{/* <Modal
233+
open={showModal}
234+
onClose={() => setShowModal(false)}
235+
aria-labelledby="modal-modal-title"
236+
aria-describedby="modal-modal-description">
237+
<Box>
238+
<Typography id="modal-modal-title" variant="h6" component="h2">
239+
Add a comment
240+
</Typography>
241+
<textarea name="" id="" cols="30" rows="10" onChange={(e) => setComment(e.target.value)}>
242+
243+
</textarea>
244+
<Button>Mark as Reconciled</Button>
245+
</Box>
246+
</Modal> */}
247+
<Dialog
248+
title="Dialog"
249+
modal={true}
250+
maxWidth={"xl"}
251+
open={showModal}
252+
onClose={() => setShowModal(false)}
253+
aria-labelledby="simple-modal-title"
254+
aria-describedby="simple-modal-description">
255+
<DialogContent>
256+
<h4>Add Comment</h4>
257+
<textarea placeholder="Comments..." name="" id="" cols="30" rows="10" onChange={(e) => setComment(e.target.value)}></textarea>
258+
<Button variant="outlined" disabled={!!!comment} onClick={() => handleMarkReconciled()}>Mark as Reconciled</Button>
259+
</DialogContent>
260+
</Dialog>
261+
</div>
262+
)
263+
}

styles/pages/global.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@import "../../../node_modules/@syncfusion/ej2-base/styles/material.css";
22
@import "../../../node_modules/@syncfusion/ej2-inputs/styles/material.css";
33
@import "../../../node_modules/@syncfusion/ej2-dropdowns/styles/material.css";
4+
45
.title {
56
font-weight: 100;
67
}
@@ -42,3 +43,15 @@ select {
4243
form {
4344
width: 100%;
4445
}
46+
47+
.border {
48+
border: solid 1px black !important;
49+
}
50+
51+
.bg-blue {
52+
background-color: #c9dfff;
53+
}
54+
55+
.bg-green {
56+
background-color: #b2e4bb;
57+
}

0 commit comments

Comments
 (0)