️ This Gitlab will be shut down at 2021-12-31 23:59:59.
Students and staff can migrate to gitlab.utwente.nl.
SNT members can migrate to gitlab.snt.utwente.nl.
Contact bestuur@snt.utwente.nl for more information.

Migrate your projects today!
Export your project using the webinterface or use a script.

Commit 5d0e1abb authored by TJHeeringa's avatar TJHeeringa

Fixed stuff for debt collection

parent a477c4ba
/* eslint-disable no-use-before-define */
import { makeStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Autocomplete from "@material-ui/lab/Autocomplete";
import React from "react";
const useStyles = makeStyles({
option: {
fontSize: 15,
"& > span": {
marginRight: 10,
fontSize: 18,
},
},
});
const MemberField = (props) => {
const classes = useStyles();
return (
<Autocomplete
id={"country-select-demo"}
style={{ width: "100%" }}
options={countries}
classes={{
option: classes.option,
}}
autoHighlight
getOptionLabel={(option) => option.label}
renderOption={(option) => (
<React.Fragment>
<span>{ countryToFlag(option.code) }</span>
{ option.label }
</React.Fragment>
)}
renderInput={(params) => (
<TextField
{...params}
variant={"outlined"}
inputProps={{
...params.inputProps,
autoComplete: "new-password", // disable autocomplete and autofill
}}
/>
)}
{...props}
/>
);
};
export default MemberField;
\ No newline at end of file
import {Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@material-ui/core";
import {Dialog, DialogContent, DialogContentText, DialogTitle} from "@material-ui/core";
import Button from "@material-ui/core/Button";
import Divider from "@material-ui/core/Divider";
import Modal from "@material-ui/core/Modal";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import PropTypes from "prop-types";
import React from "react";
import Wrapper from "../Fields/Wrapper";
const useStyles = makeStyles(theme => ({
paper: {
width: 400,
backgroundColor: theme.palette.background.paper,
margin: "auto",
padding: theme.spacing(2, 4, 3),
top: "50%",
position: "relative",
transform: "translateY(-50%)",
borderRadius: "0.3rem"
},
buttonGroup: {
justifyContent: "space-between",
display: "flex"
},
divider: {
marginTop: "15px",
marginBottom: "15px"
}
}));
const ConfirmationModal = ({ onConfirm, onCancel, title, description, open, cancelButtonText, confirmButtonText, secondaryAction, secondaryButtonText, size }) => {
return (
<Dialog
......@@ -63,30 +37,6 @@ const ConfirmationModal = ({ onConfirm, onCancel, title, description, open, canc
);
};
const ConfirmationModal2 = ({ onConfirm, onCancel, title, description, open, cancelButtonText, confirmButtonText, secondaryAction, secondaryButtonText, size }) => {
const classes = useStyles();
return (
<Modal
open={open}
size={size}
aria-labelledby={"contained-modal-title-vcenter"}
>
<div className={classes.paper}>
<Typography variant={"h5"}>{ title }</Typography>
<Divider className={classes.divider}/>
<Typography variant={"body1"}>{ description }</Typography>
<Divider className={classes.divider}/>
<Wrapper>
<Button variant={"contained"} onClick={onCancel}>{ cancelButtonText }</Button>
<Button variant={"contained"} color={"secondary"} onClick={secondaryAction}>{ secondaryButtonText }</Button>
<Button variant={"contained"} color={"primary"} onClick={onConfirm}>{ confirmButtonText }</Button>
</Wrapper>
</div>
</Modal>
);
};
ConfirmationModal.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
......@@ -105,7 +55,7 @@ ConfirmationModal.defaultProps = {
cancelButtonText: "Cancel",
confirmButtonText: "Confirm",
secondaryButtonText: "",
size: "m"
size: "md"
};
export default ConfirmationModal;
\ No newline at end of file
......@@ -11,11 +11,12 @@ import SpecificDataFieldForm from "../../Components/Forms/SpecificDataFieldForm"
import Block from "../../Components/PageLayout/Content/Block";
import {useDataFields} from "../../Contexts/Members";
import Tooltip from "@material-ui/core/Tooltip";
import {orderBy} from "lodash";
const AssociationDataFields = ({ association }) => {
const DataFieldsApi = useDataFields();
const specific_data_fields = DataFieldsApi.list();
const specific_data_fields = orderBy(DataFieldsApi.list(), ["name"], ["asc"]);
const addAssociationSpecificDataField = (field) => DataFieldsApi.add(field);
const updateAssociationSpecificDataField = (updated_field) => DataFieldsApi.edit(updated_field, "slug");
......
......@@ -13,7 +13,7 @@ import FormModal from "../../Components/Modals/FormModal";
const ImportModal = ({title, cancelButtonText, submitButtonText, open, onCancel, onSubmit}) => {
const [csv, setCsv] = useState();
const [delimiter, setDelimiter] = useState("");
console.log(csv);
return (
<FormModal
title={title}
......
......@@ -4,69 +4,129 @@ import ExtremeTable from "App/Components/Tables/ExtremeTable";
import { snakeCase } from "lodash";
import moment from "moment";
import PropTypes from "prop-types";
import React, {useState} from "react";
import React, {useMemo, useState} from "react";
import CardGrid from "../../Components/Card/CardGrid";
import Wrapper from "../../Components/Fields/Wrapper";
import useModalState from "../../Components/Hooks/useModalState";
import ConfirmationModal from "../../Components/Modals/ConfirmationModal";
import Block from "../../Components/PageLayout/Content/Block";
const headers = ["Who", "Amount", "Identifier", "Bank Account", "Email", "Cancellation"];
// const headers = ["Who", "Amount", "Identifier", "Bank Account", "Email", "Cancellation"];
const headers = [
{name: "who", title: "Who"},
{name: "amount", title: "Amount"},
{name: "identifier", title: "Identifier"},
{name: "iban", title: "IBAN"},
{name: "bic", title: "BIC"},
{name: "email", title: "Email"},
];
const editingStateColumnExtensions = [
{ columnName: "who", editingEnabled: false },
{ columnName: "amount", editingEnabled: false },
{ columnName: "identifier", editingEnabled: false },
{ columnName: "bank_account", editingEnabled: false },
{ columnName: "iban", editingEnabled: false },
{ columnName: "bic", editingEnabled: false },
{ columnName: "email", editingEnabled: false },
{ columnName: "stornering", editingEnabled: true },
{ columnName: "cancellation", editingEnabled: true },
];
const titleLockModal = "Lock debt collection";
const titleRemoveModal = "Remove debt collection";
const descriptionLockModal = "This locks the debt collection. Do this after and only after you have submitted this debt collection to your bank.";
const descriptionRemoveModal = "Are you sure you want to remove this debt collection?";
const DebtCollectionDetail = ({debtCollection, ...props}) => {
const DebtCollectionDetail = ({debtCollection, toggleLockDebtCollection, removeDebtCollection, ...props}) => {
const morphDataToDebtEntry = (data) => {
return {
who: data.profile.surname,
who: data.profile.given_name + " " + data.profile.surname,
amount: data.total_price,
identifier: undefined,
bank_account: data.profile.iban,
identifier: "",
iban: data.profile.iban,
bic: data.profile.bic_code,
email: data.profile.email,
cancellation: false
};
};
const extremeHeaders = headers.map(header=>{return {name: snakeCase(header), title: header};});
const rows = debtCollection.debt_entries ? debtCollection.debt_entries.map(entry=>morphDataToDebtEntry(entry)) : [];
const [selection, setSelection] = useState([]);
const [lockModalOpen, toggleLockModalOpen] = useModalState(false);
const [removeModalOpen, toggleRemoveModalOpen] = useModalState(false);
const onCancelRemove = () => {
toggleRemoveModalOpen();
};
const onCancelLock = () => {
toggleLockModalOpen();
};
const onConfirmRemove = () => {
toggleRemoveModalOpen();
removeDebtCollection();
};
const onConfirmLock = () => {
toggleLockModalOpen();
toggleLockDebtCollection();
};
const hideButtons = useMemo(()=>!debtCollection.slug, [debtCollection]);
const locked = useMemo(()=>debtCollection.locked, [debtCollection]);
return (
<Block>
<Wrapper>
<Typography variant={"h4"}>
Debt Collection { debtCollection.debt_collection_date && "of " + moment(debtCollection.debt_collection_date).format("LLLL") }
</Typography>
<div>
<Button variant={"outlined"} color={"secondary"}>Delete Debt Collection</Button>
&nbsp;
<Button variant={"outlined"}>Lock</Button>
</div>
</Wrapper>
<hr className={"box-title-separator"}/>
<ExtremeTable
headers={extremeHeaders}
rows={rows || []}
booleanColumns={["stornering"]}
showExporter={true}
showGrouping={false}
showSelect={false}
editingStateColumnExtensions={editingStateColumnExtensions}
showEditing={true}
selection={{selection: selection, setSelection: setSelection}}
{...props}
<>
<Block>
<Wrapper>
<Typography variant={"h4"}>
Debt Collection { debtCollection.debt_collection_date && "of " + moment(debtCollection.debt_collection_date).format("LLLL") }
</Typography>
<div>
{ hideButtons ||
<>
{ locked || <Button variant={"outlined"} color={"secondary"} onClick={toggleRemoveModalOpen}>Delete Debt Collection</Button> }
&nbsp;
<Button disabled={locked} variant={"outlined"} onClick={toggleLockModalOpen}>{ locked ? "Locked" : "Lock" }</Button>
</>
}
</div>
</Wrapper>
<hr className={"box-title-separator"}/>
<ExtremeTable
headers={headers}
rows={rows || []}
booleanColumns={["stornering"]}
showExporter={true}
showGrouping={false}
showSelect={false}
editingStateColumnExtensions={editingStateColumnExtensions}
showEditing={true}
selection={{selection: selection, setSelection: setSelection}}
{...props}
/>
</Block>
<ConfirmationModal
title={titleLockModal}
onCancel={onCancelLock}
onConfirm={onConfirmLock}
open={lockModalOpen}
description={descriptionLockModal}
confirmButtonText={"Lock"}
/>
<ConfirmationModal
title={titleRemoveModal}
onCancel={onCancelRemove}
onConfirm={onConfirmRemove}
open={removeModalOpen}
description={descriptionRemoveModal}
confirmButtonText={"Remove"}
/>
</Block>
</>
);
};
DebtCollectionDetail.propTypes = {
debtCollection: PropTypes.object
debtCollection: PropTypes.object,
removeDebtCollection: PropTypes.func.isRequired,
toggleLockDebtCollection: PropTypes.func.isRequired
};
CardGrid.defaultProps = {
......
import {Checkbox, Input, MenuItem, Select} from "@material-ui/core";
import Button from "@material-ui/core/Button";
import Grid from "@material-ui/core/Grid";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import CalendarToday from "@material-ui/icons/CalendarToday";
import FormModal from "App/Components/Modals/FormModal";
import { useAlertHandler } from "App/Contexts/AlertHandler";
import moment from "moment/moment";
import PropTypes from "prop-types";
import React from "react";
import useModalState from "../../Components/Hooks/useModalState";
import Block from "../../Components/PageLayout/Content/Block";
import {useAPI} from "../../Contexts/API";
import DebtCollectionNew from "./DebtCollectionNew";
import useModalState from "../../Components/Hooks/useModalState";
const useStyles = makeStyles((theme) => ({
......@@ -93,7 +90,7 @@ const DebtCollectionList = ({ debtCollectionApi, eventsApi, association, setSele
<hr className={"box-title-separator"}/>
{ debtCollections && debtCollections.map((debtCollection,d)=>
<ListItem button key={d} onClick={()=>handleDebtCollectionSelection(debtCollection)}>
<ListItemText primary={debtCollection.debt_collection_date} secondary={""+debtCollection.amount}/>
<ListItemText primary={moment(debtCollection.debt_collection_date).local().format("YYYY-MM-DD HH:mm:ss")} secondary={""+debtCollection.amount}/>
</ListItem>
) }
</List>
......
......@@ -13,6 +13,7 @@ import Wrapper from "../../Components/Fields/Wrapper";
import {useAPI} from "../../Contexts/API";
import IconHolder from "../../Components/Fields/IconHolder";
import moment from "moment";
import Button from "@material-ui/core/Button";
const DebtCollectionNew = ({ debtCollection: propDebtCollection, open, onCreate, onCancel, association, events }) => {
......@@ -26,8 +27,15 @@ const DebtCollectionNew = ({ debtCollection: propDebtCollection, open, onCreate,
setDebtCollectionChanges(prevState=>({ ...prevState, [field]: value }));
};
const handleAddAllEvents = () => {
setDebtCollectionChanges(prevState=>({ ...prevState, include_events: events.map(event=>event.url) }));
};
const handleClearEvents = () => {
setDebtCollectionChanges(prevState=>({ ...prevState, include_events: [] }));
};
const addDebtCollection = () => {
console.log(debtCollection);
API.callv4({
url: "/debt_collections",
method: "POST",
......@@ -60,8 +68,8 @@ const DebtCollectionNew = ({ debtCollection: propDebtCollection, open, onCreate,
<IconHolder Icon={CalendarToday}/>
<DateTimeField
name={"Debt Collection Date"}
value={debtCollection.debt_collection_date}
onChange={date => handleDebtCollectionChange("debt_collection_date", date)}
value={moment(debtCollection.debt_collection_date).local().format("YYYY-MM-DD HH:mm")}
onChange={date => handleDebtCollectionChange("debt_collection_date", moment(date).utc())}
helperText={"This is the date you are planning on collection the debt collection."}
/>
</Wrapper>
......@@ -85,6 +93,10 @@ const DebtCollectionNew = ({ debtCollection: propDebtCollection, open, onCreate,
</MenuItem>
)) }
</SelectField>
&nbsp;
<Button variant={"outlined"} onClick={handleAddAllEvents}>All events</Button>
&nbsp;
<Button variant={"outlined"} onClick={handleClearEvents}>Clear events</Button>
</Wrapper>
<Wrapper>
<CheckboxField
......@@ -121,7 +133,7 @@ const DebtCollectionNew = ({ debtCollection: propDebtCollection, open, onCreate,
DebtCollectionNew.propTypes = {
association: PropTypes.object.isRequired,
debtCollection: PropTypes.object,
open: PropTypes.object.isRequired,
open: PropTypes.bool.isRequired,
onCreate: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
events: PropTypes.array.isRequired
......@@ -131,7 +143,7 @@ DebtCollectionNew.defaultProps = {
debtCollection: {
include_events: [],
include_membership_fee: false,
debt_collection_date: moment().add(1,"days").utc(true),
debt_collection_date: moment().add(1,"days").utc(),
include_invoices: false,
ignore_threshold: false
},
......
import Grid from "@material-ui/core/Grid";
import {makeStyles} from "@material-ui/core/styles";
import PropTypes from "prop-types";
import React, {useState} from "react";
import React, {useCallback, useState} from "react";
import useCollectionAPI from "../../Components/Hooks/useCollectionAPI";
import DebtCollectionDetail from "./DebtCollectionDetail";
import DebtCollectionList from "./DebtCollectionList";
import {useAPI} from "../../Contexts/API";
import {useAlertHandler} from "../../Contexts/AlertHandler";
const useStyles = makeStyles((theme) => ({
root: {
......@@ -34,14 +35,45 @@ const useStyles = makeStyles((theme) => ({
}));
const DebtCollectionsRouter = ({ association }) => {
const DebtCollectionsPage = ({ association, eventsAPI, DebtCollectionApi }) => {
const classes = useStyles();
const DebtCollectionApi = useCollectionAPI("/debt_collections", {limit: 10000, association__slug: association.slug, ordering: "-debt_collection_date"});
const eventsAPI = useCollectionAPI("/events", {limit: 10000, association__slug: association.slug});
const Api = useAPI();
const alerthandler = useAlertHandler();
const [selectedDebtCollection, setSelectedDebtCollection] = useState({});
const toggleLockDebtCollection = useCallback(()=>{
console.log(selectedDebtCollection);
Api.callv4({
url: selectedDebtCollection.url,
method: "PATCH",
object: {locked: !selectedDebtCollection.locked},
on_succes: (debtCollection) => {
alerthandler.success("Succesfully locked debt collection");
DebtCollectionApi.edit(debtCollection, "slug", true);
setSelectedDebtCollection(debtCollection);
},
on_failure: () => {
alerthandler.failure("Failed to lock debt collection");
}
});
}, [selectedDebtCollection]);
const removeDebtCollection = useCallback(()=>{
Api.callv4({
url: selectedDebtCollection.url,
method: "DELETE",
on_succes: () => {
alerthandler.success("Succesfully removed debt collection");
DebtCollectionApi.remove(selectedDebtCollection, "url");
setSelectedDebtCollection({});
},
on_failure: () => {
alerthandler.failure("Removal of debt collection failed");
}
});
},[DebtCollectionApi]);
return (
<Grid container spacing={1} className={classes.paper}>
<Grid item xs={3}>
......@@ -55,18 +87,20 @@ const DebtCollectionsRouter = ({ association }) => {
<Grid item className={classes.detail} xs={9}>
<DebtCollectionDetail
debtCollection={selectedDebtCollection}
toggleLockDebtCollection={toggleLockDebtCollection}
removeDebtCollection={removeDebtCollection}
/>
</Grid>
</Grid>
);
};
DebtCollectionsRouter.propTypes = {
DebtCollectionsPage.propTypes = {
association: PropTypes.object.isRequired
};
DebtCollectionsRouter.defaultProps = {
DebtCollectionsPage.defaultProps = {
};
export default DebtCollectionsRouter;
\ No newline at end of file
export default DebtCollectionsPage;
\ No newline at end of file
import PropTypes from "prop-types";
import React from "react";
import { Route, Switch } from "react-router-dom";
import useCollectionAPI from "../../Components/Hooks/useCollectionAPI";
import DebtCollectionsPage from "./DebtCollectionsPage";
import Invoices from "./Invoices";
import FinancialSettings from "./Settings";
const FinancialRouter = ({ association, path }) => {
const DebtCollectionApi = useCollectionAPI("/debt_collections", {limit: 10000, association__slug: association.slug, ordering: "-debt_collection_date", expand: "debt_entries"});
const eventsAPI = useCollectionAPI("/events", {limit: 10000, association__slug: association.slug});
return (
<Switch>
<Route path={path+"/financial/debt_collections"}>
<DebtCollectionsPage
association={association}
DebtCollectionApi={DebtCollectionApi}
eventsAPI={eventsAPI}
path={path}
/>
</Route>
<Route path={path+"/financial/settings"}>
<FinancialSettings
association={association}
path={path}
/>
</Route>
<Route path={path+"/financial/invoices"}>
<Invoices
DebtCollectionApi={DebtCollectionApi}
association={association}
path={path}
/>
</Route>
</Switch>
);
};
FinancialRouter.propTypes = {
association: PropTypes.object.isRequired,
path: PropTypes.string.isRequired
};
export default FinancialRouter;
\ No newline at end of file
import {
Plugin, Template, TemplateConnector, TemplatePlaceholder,
} from "@devexpress/dx-react-core";
import { EditingState } from "@devexpress/dx-react-grid";
import {
Grid,
Table,
TableEditColumn,
TableHeaderRow,
} from "@devexpress/dx-react-grid-material-ui";
import {MenuItem} from "@material-ui/core";
import Button from "@material-ui/core/Button";
import Container from "@material-ui/core/Container";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogTitle from "@material-ui/core/DialogTitle";
import Paper from "@material-ui/core/Paper";
import React, {useEffect, useMemo, useState} from "react";
import Typography from "@material-ui/core/Typography";
import DescriptionIcon from "@material-ui/icons/Description";
import EuroIcon from "@material-ui/icons/Euro";
import EventIcon from "@material-ui/icons/Event";
import PersonIcon from "@material-ui/icons/Person";
import RefreshIcon from "@material-ui/icons/Refresh";
import Papa from "papaparse";