├── backend ├── api │ ├── __init__.py │ ├── models.py │ ├── tests.py │ ├── admin.py │ ├── apps.py │ ├── urls.py │ ├── serializers.py │ └── views.py ├── accounts │ ├── __init__.py │ ├── models.py │ ├── tests.py │ ├── admin.py │ ├── urls.py │ ├── apps.py │ ├── forms.py │ └── views.py ├── journal │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0002_alter_ledger_code_alter_ledgertype_code_and_more.py │ ├── tests.py │ ├── views.py │ ├── apps.py │ └── admin.py ├── peoples │ ├── __init__.py │ ├── tests.py │ ├── migrations │ │ └── __init__.py │ ├── filters │ │ └── staff_filters.py │ ├── apps.py │ ├── permissions.py │ ├── admin.py │ ├── urls.py │ ├── serializers.py │ ├── models.py │ └── views.py ├── report │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0003_remove_cihcalculation_organization.py │ │ ├── 0002_rename_cache_in_hand_cihcalculation_cash_in_hand.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── views.py │ ├── apps.py │ ├── admin.py │ └── models.py ├── korjo_soft │ ├── __init__.py │ ├── permissions.py │ ├── asgi.py │ ├── wsgi.py │ └── urls.py ├── organization │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_remove_branch_thana.py │ │ └── 0003_alter_user_groups_alter_user_is_superuser.py │ ├── management │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── prepare_org.py │ ├── signals │ │ └── signals.py │ ├── urls.py │ ├── apps.py │ ├── forms.py │ ├── tests.py │ ├── managers.py │ ├── paginations │ │ └── paginations.py │ ├── routes.py │ ├── admin.py │ └── views.py ├── transaction │ ├── __init__.py │ ├── dashboard_views.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_transactioncategory_category_type.py │ │ └── 0003_remove_generaltransaction_organization_and_more.py │ ├── tests.py │ ├── apps.py │ ├── urls.py │ ├── routes.py │ ├── admin.py │ ├── forms.py │ ├── serializers.py │ └── utils.py ├── runtime.txt ├── static │ ├── src │ │ ├── scss │ │ │ ├── core │ │ │ │ ├── _mobile.scss │ │ │ │ ├── _badge.scss │ │ │ │ ├── _breadcrumb.scss │ │ │ │ ├── _utilities.scss │ │ │ │ ├── _others.scss │ │ │ │ ├── utilities │ │ │ │ │ ├── _background.scss │ │ │ │ │ ├── _display.scss │ │ │ │ │ └── _borders.scss │ │ │ │ ├── _dropdown-menu-right.scss │ │ │ │ ├── _input-group.scss │ │ │ │ ├── _charts.scss │ │ │ │ ├── _footer.scss │ │ │ │ ├── _modal.scss │ │ │ │ ├── _progress.scss │ │ │ │ ├── _tables.scss │ │ │ │ ├── _grid.scss │ │ │ │ ├── _animate.scss │ │ │ │ ├── _typography.scss │ │ │ │ ├── _nav.scss │ │ │ │ ├── _breadcrumb-menu.scss │ │ │ │ ├── _temp.scss │ │ │ │ ├── core.scss │ │ │ │ ├── _avatars.scss │ │ │ │ ├── _callout.scss │ │ │ │ ├── _dropdown.scss │ │ │ │ ├── _aside.scss │ │ │ │ └── _navbar.scss │ │ │ ├── _custom.scss │ │ │ ├── vendors │ │ │ │ ├── _variables.scss │ │ │ │ └── chart.js │ │ │ │ │ └── chart.scss │ │ │ └── style.scss │ │ ├── img │ │ │ ├── logo.png │ │ │ ├── avatars │ │ │ │ ├── 4.jpg │ │ │ │ ├── 6.jpg │ │ │ │ └── 7.jpg │ │ │ ├── favicon.png │ │ │ └── logo-symbol.png │ │ └── vendors │ │ │ └── css │ │ │ ├── chart.min.css │ │ │ └── chart.css │ ├── gulp-tasks │ │ └── vendors.js │ ├── package.json │ ├── gulpfile.js │ └── vendors.json ├── templates │ ├── transaction │ │ ├── dashboard.html │ │ ├── loan_list.html │ │ ├── deposit_list.html │ │ ├── loan_disbursement_form.html │ │ ├── deposit_posting.html │ │ └── installment_posting.html │ ├── base │ │ ├── footer.html │ │ ├── sidebar.html │ │ ├── breadcrumb.html │ │ ├── js.html │ │ ├── header.html │ │ ├── navbar.html │ │ └── base.html │ ├── auth │ │ └── login.html │ └── org │ │ └── team_list.html ├── .env.examples ├── Dockerfile ├── .dockerignore ├── docker-compose.yml ├── manage.py └── requirements.txt ├── frontend ├── .dockerignore ├── src │ ├── hooks │ │ ├── index.ts │ │ └── useIsClient.ts │ ├── utils │ │ ├── constants.ts │ │ ├── isStringMatched.ts │ │ ├── randomNumber.ts │ │ ├── decoder.ts │ │ ├── getHeaders.ts │ │ ├── removeNullOrEmpty.ts │ │ ├── zodSchemaParse.ts │ │ ├── getWeekNoOfCurrentMonth.ts │ │ ├── messages.ts │ │ ├── isRouterPathnameMatched.ts │ │ ├── http.ts │ │ ├── sweatalert.ts │ │ ├── zodSafeQuery.ts │ │ └── datetime.ts │ ├── schema │ │ ├── AllModalSchema.ts │ │ ├── TeamSchema.ts │ │ └── AuthSchema.ts │ ├── Layouts │ │ ├── DashboardLayout │ │ │ └── index.tsx │ │ └── BaseLayout │ │ │ └── index.tsx │ ├── types │ │ ├── memberSaving.type.ts │ │ ├── incomeExpense.type.ts │ │ └── memberInstallment.type.ts │ ├── components │ │ ├── Footer │ │ │ └── index.tsx │ │ ├── CommonBreadCrumb │ │ │ └── index.tsx │ │ ├── Navbar │ │ │ └── index.tsx │ │ ├── CustomDatePicker │ │ │ └── CustomDatePicker.tsx │ │ ├── CustomInput │ │ │ └── index.tsx │ │ ├── CustomSelect │ │ │ └── index.tsx │ │ ├── Pagination │ │ │ └── Pagination.tsx │ │ └── DashboardNavbar │ │ │ └── index.tsx │ ├── icons │ │ ├── FilterIcon.tsx │ │ ├── PlusIcon.tsx │ │ ├── NextIcon.tsx │ │ ├── PreviousIcon.tsx │ │ ├── SearchIcon.tsx │ │ ├── ExpenseIcon.tsx │ │ ├── LoanPlusIcon.tsx │ │ ├── LoanIcon.tsx │ │ ├── PersonPlusIcon.tsx │ │ ├── DepositIcon.tsx │ │ ├── WithdrawIcon.tsx │ │ ├── CalenderIcon.tsx │ │ ├── index.tsx │ │ ├── PersonsIcon.tsx │ │ ├── IncomeIcon.tsx │ │ ├── VerticalDotIcon.tsx │ │ ├── BranchIcon.tsx │ │ └── CashHandIcon.tsx │ ├── modules │ │ ├── team │ │ │ ├── stores │ │ │ │ ├── useMemberSavingsStore.ts │ │ │ │ └── useMemberInstallmentsStore.ts │ │ │ └── components │ │ │ │ └── EditGroupModal.tsx │ │ ├── branch │ │ │ └── components │ │ │ │ ├── EditBranchModal.tsx │ │ │ │ └── CreateBranchModal.tsx │ │ └── dashboard │ │ │ └── components │ │ │ └── Chart.tsx │ └── env.mjs ├── styles │ ├── tailwind.css │ └── globals.css ├── public │ ├── favicon.ico │ ├── fonts │ │ ├── Nikosh.ttf │ │ ├── Kalpurush.ttf │ │ ├── TiroBangla.ttf │ │ └── LiAlinurBoisakhUnicode-Regular.ttf │ ├── vercel.svg │ ├── thirteen.svg │ └── next.svg ├── postcss.config.js ├── i18n.json ├── .env.examples ├── locales │ ├── en │ │ ├── common.json │ │ └── home.json │ └── hi │ │ ├── common.json │ │ └── home.json ├── .prettierrc.json ├── pages │ ├── member │ │ ├── transactions.tsx │ │ ├── installmentGraph.tsx │ │ └── depositeChart.tsx │ └── _document.tsx ├── docker-compose.yml ├── Dockerfile ├── next.config.js ├── .eslintrc.json ├── .gitignore ├── .vscode │ └── settings.json ├── tsconfig.json ├── middleware.ts ├── package.json ├── README.md └── tailwind.config.js ├── package.json ├── yarn.lock ├── .github └── workflows │ └── auto-assign.yml.yml ├── .pre-commit-config.yaml └── README.md /backend/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/accounts/models.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/journal/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/peoples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/peoples/tests.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/report/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/korjo_soft/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/organization/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/transaction/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/journal/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/peoples/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/report/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.10.2 2 | -------------------------------------------------------------------------------- /backend/transaction/dashboard_views.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/organization/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/peoples/filters/staff_filters.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_mobile.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/transaction/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /backend/organization/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useIsClient'; 2 | -------------------------------------------------------------------------------- /backend/static/src/scss/_custom.scss: -------------------------------------------------------------------------------- 1 | // Here you can add other styles 2 | -------------------------------------------------------------------------------- /frontend/src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const itemsLoadPerPage: number = 5; 2 | -------------------------------------------------------------------------------- /backend/api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /backend/api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "moment-timezone": "^0.5.43" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /backend/accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /backend/journal/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/report/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/report/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /backend/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /backend/journal/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /backend/transaction/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /frontend/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_badge.scss: -------------------------------------------------------------------------------- 1 | .badge-pill { 2 | border-radius: $badge-pill-border-radius; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harunurkst/qard-al-hasana/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /backend/static/src/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harunurkst/qard-al-hasana/HEAD/backend/static/src/img/logo.png -------------------------------------------------------------------------------- /frontend/public/fonts/Nikosh.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harunurkst/qard-al-hasana/HEAD/frontend/public/fonts/Nikosh.ttf -------------------------------------------------------------------------------- /backend/static/src/img/avatars/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harunurkst/qard-al-hasana/HEAD/backend/static/src/img/avatars/4.jpg -------------------------------------------------------------------------------- /backend/static/src/img/avatars/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harunurkst/qard-al-hasana/HEAD/backend/static/src/img/avatars/6.jpg -------------------------------------------------------------------------------- /backend/static/src/img/avatars/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harunurkst/qard-al-hasana/HEAD/backend/static/src/img/avatars/7.jpg -------------------------------------------------------------------------------- /backend/static/src/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harunurkst/qard-al-hasana/HEAD/backend/static/src/img/favicon.png -------------------------------------------------------------------------------- /frontend/public/fonts/Kalpurush.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harunurkst/qard-al-hasana/HEAD/frontend/public/fonts/Kalpurush.ttf -------------------------------------------------------------------------------- /frontend/public/fonts/TiroBangla.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harunurkst/qard-al-hasana/HEAD/frontend/public/fonts/TiroBangla.ttf -------------------------------------------------------------------------------- /backend/static/src/img/logo-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harunurkst/qard-al-hasana/HEAD/backend/static/src/img/logo-symbol.png -------------------------------------------------------------------------------- /backend/static/src/scss/core/_breadcrumb.scss: -------------------------------------------------------------------------------- 1 | .breadcrumb { 2 | position: relative; 3 | @include borders($breadcrumb-borders); 4 | } 5 | -------------------------------------------------------------------------------- /backend/organization/signals/signals.py: -------------------------------------------------------------------------------- 1 | from django.dispatch import receiver 2 | from django.db.models.signals import pre_save, post_save 3 | 4 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_utilities.scss: -------------------------------------------------------------------------------- 1 | @import "utilities/background"; 2 | @import "utilities/borders"; 3 | @import "utilities/display"; 4 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_others.scss: -------------------------------------------------------------------------------- 1 | // scss-lint:disable QualifyingElement 2 | hr.transparent { 3 | border-top: 1px solid transparent; 4 | } 5 | -------------------------------------------------------------------------------- /backend/accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import login_view 3 | 4 | 5 | urlpatterns = [ 6 | path("login/", login_view, name="login") 7 | ] -------------------------------------------------------------------------------- /backend/organization/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import * 3 | 4 | 5 | urlpatterns = [ 6 | path("teams", team_list, name="team_list"), 7 | ] -------------------------------------------------------------------------------- /frontend/public/fonts/LiAlinurBoisakhUnicode-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harunurkst/qard-al-hasana/HEAD/frontend/public/fonts/LiAlinurBoisakhUnicode-Regular.ttf -------------------------------------------------------------------------------- /frontend/i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "locales": ["en", "bn"], 3 | "defaultLocale": "en", 4 | "pages": { 5 | "*": ["common"], 6 | "/": ["home"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'api' 7 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/utilities/_background.scss: -------------------------------------------------------------------------------- 1 | .bg-primary, 2 | .bg-success, 3 | .bg-info, 4 | .bg-warning, 5 | .bg-danger, 6 | .bg-inverse { 7 | color: #fff; 8 | } 9 | -------------------------------------------------------------------------------- /backend/templates/transaction/dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends 'base/base.html' %} 2 | 3 | 4 | 5 | {% block content %} 6 | 7 |

Dashboard page

8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /backend/journal/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class JournalConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "journal" 7 | -------------------------------------------------------------------------------- /backend/peoples/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PeoplesConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'peoples' 7 | -------------------------------------------------------------------------------- /backend/report/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ReportConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "report" 7 | -------------------------------------------------------------------------------- /backend/accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'accounts' 7 | -------------------------------------------------------------------------------- /frontend/src/utils/isStringMatched.ts: -------------------------------------------------------------------------------- 1 | const isStringMatched = (str: string, matchStr: string): boolean => { 2 | return str.includes(matchStr); 3 | }; 4 | 5 | export default isStringMatched; 6 | -------------------------------------------------------------------------------- /frontend/src/utils/randomNumber.ts: -------------------------------------------------------------------------------- 1 | function randomNumber(to: number, from: number) { 2 | return Math.floor(Math.random() * (from - to + 1)) + to; 3 | } 4 | 5 | export default randomNumber; 6 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_dropdown-menu-right.scss: -------------------------------------------------------------------------------- 1 | // Temp fix for reactstrap 2 | .app-header { 3 | .navbar-nav { 4 | .dropdown-menu-right { 5 | right: auto; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_input-group.scss: -------------------------------------------------------------------------------- 1 | .input-group-addon, 2 | .input-group-btn { 3 | min-width: 40px; 4 | white-space: nowrap; 5 | vertical-align: middle; // Match the inputs 6 | } 7 | -------------------------------------------------------------------------------- /backend/organization/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class OrganizationConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'organization' 7 | -------------------------------------------------------------------------------- /backend/transaction/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TransactionConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'transaction' 7 | -------------------------------------------------------------------------------- /frontend/.env.examples: -------------------------------------------------------------------------------- 1 | SKIP_ENV_VALIDATION=false 2 | NODE_ENV=development 3 | NEXT_PUBLIC_API_URL=http://localhost:3000/ 4 | NEXT_PUBLIC_BACKEND_API_URL=http://localhost:8000/ 5 | NEXTAUTH_SECRET='secret' -------------------------------------------------------------------------------- /frontend/src/utils/decoder.ts: -------------------------------------------------------------------------------- 1 | export const decoderFunction = (accesssToken: string) => { 2 | const de = require('jsonwebtoken'); 3 | const deDone = de.decode(accesssToken); 4 | return deDone; 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/utils/getHeaders.ts: -------------------------------------------------------------------------------- 1 | export const getHeaders = () => { 2 | return { 3 | headers: { 4 | Authorization: `Bearer ${localStorage.getItem("access_token")}`, 5 | }, 6 | }; 7 | }; -------------------------------------------------------------------------------- /backend/accounts/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | class LoginForm(forms.Form): 4 | username = forms.CharField(max_length=150, required=True) 5 | password = forms.CharField(widget=forms.PasswordInput, required=True) 6 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_charts.scss: -------------------------------------------------------------------------------- 1 | .chart-wrapper { 2 | canvas { 3 | width: 100% !important; 4 | } 5 | } 6 | // scss-lint:disable QualifyingElement 7 | base-chart.chart { 8 | display: block !important; 9 | } 10 | -------------------------------------------------------------------------------- /backend/organization/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from .models import Team 3 | 4 | 5 | class TeamForm(forms.ModelForm): 6 | class Meta: 7 | model = Team 8 | fields = ['name', 'branch', 'owner', 'address'] 9 | -------------------------------------------------------------------------------- /backend/peoples/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | 3 | 4 | class IsSameBranch(BasePermission): 5 | def has_object_permission(self, request, view, obj): 6 | return obj.branch == request.user.branch 7 | -------------------------------------------------------------------------------- /frontend/locales/en/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "metaTitle": "Next.js Localization with Next-Translate", 3 | "title": "Welcome to my i18n NextJS application!", 4 | "description": "I'm using the Next-Translate library to translate this page." 5 | } 6 | -------------------------------------------------------------------------------- /frontend/locales/hi/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "metaTitle": "Next.js Localization with Next-Translate", 3 | "title": "Welcome to my i18n NextJS application!", 4 | "description": "I'm using the Next-Translate library to translate this page." 5 | } 6 | -------------------------------------------------------------------------------- /backend/templates/base/footer.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_footer.scss: -------------------------------------------------------------------------------- 1 | .app-footer { 2 | display: flex; 3 | flex-wrap: wrap; 4 | align-items: center; 5 | padding: 0 $spacer; 6 | color: $footer-color; 7 | background: $footer-bg; 8 | @include borders($footer-borders); 9 | } 10 | -------------------------------------------------------------------------------- /backend/.env.examples: -------------------------------------------------------------------------------- 1 | DEBUG=True 2 | SECRET_KEY=!my_secretkey123 3 | ALLOWED_HOSTS="*" 4 | CORS_ALLOWED_ORIGINS=http://127.0.0.1:3000,http://localhost:3000,http://0.0.0.0:3000 5 | CSRF_TRUSTED_ORIGINS=http://127.0.0.1:3000,http://localhost:3000,http://0.0.0.0:3000 6 | TEST=TEST 7 | -------------------------------------------------------------------------------- /frontend/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"], 3 | "semi": true, 4 | "trailingComma": "es5", 5 | "singleQuote": true, 6 | "tabWidth": 4, 7 | "useTabs": false, 8 | "printWidth": 120 9 | } 10 | -------------------------------------------------------------------------------- /backend/report/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import CIHCalculation 3 | 4 | 5 | class CacheInHandAdmin(admin.ModelAdmin): 6 | list_display = ("id", "branch", "date", "cash_in_hand") 7 | 8 | 9 | admin.site.register(CIHCalculation, CacheInHandAdmin) 10 | -------------------------------------------------------------------------------- /frontend/pages/member/transactions.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | 4 | const Transactions = () => { 5 | return ( 6 | <> 7 |
8 | 9 | 10 |
11 | 12 | ) 13 | } 14 | 15 | export default Transactions -------------------------------------------------------------------------------- /frontend/src/hooks/useIsClient.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function useIsClient() { 4 | const [isClient, setClient] = useState(false); 5 | 6 | useEffect(() => { 7 | setClient(true); 8 | }, []); 9 | 10 | return isClient; 11 | } 12 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_modal.scss: -------------------------------------------------------------------------------- 1 | @each $color, $value in $theme-colors { 2 | .modal-#{$color} { 3 | 4 | .modal-content { 5 | border-color: $value; 6 | } 7 | 8 | .modal-header { 9 | color: #fff; 10 | background-color: $value; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/static/src/scss/vendors/_variables.scss: -------------------------------------------------------------------------------- 1 | @import "node_modules/bootstrap/scss/functions"; 2 | @import "../bootstrap-variables"; 3 | @import "node_modules/bootstrap/scss/variables"; 4 | @import "node_modules/bootstrap/scss/mixins"; 5 | @import "../core-variables"; 6 | @import "../core/variables"; 7 | -------------------------------------------------------------------------------- /frontend/locales/en/home.json: -------------------------------------------------------------------------------- 1 | { 2 | "metaTitle": "Next.js Localization with Next-Translate", 3 | "title": "Welcome to my i18n NextJS application!", 4 | "pageTitle": "Today’s Featured Products", 5 | "pageShortDescription": "Featured products are handpicked by our team, which are worth trying." 6 | } 7 | -------------------------------------------------------------------------------- /frontend/locales/hi/home.json: -------------------------------------------------------------------------------- 1 | { 2 | "metaTitle": "Next.js Localization with Next-Translate", 3 | "title": "Welcome to my i18n NextJS application!", 4 | "pageTitle": "आज के चुनिंदा उत्पाद", 5 | "pageShortDescription": "विशेष रुप से प्रदर्शित उत्पाद हमारी टीम द्वारा चुने गए हैं, जो कोशिश करने लायक हैं।" 6 | } 7 | -------------------------------------------------------------------------------- /backend/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import GetMemberView, SavingsPostingAPI 3 | 4 | urlpatterns =[ 5 | path('member//', GetMemberView.as_view(), name='api_get_member'), 6 | path('savings-posting/', SavingsPostingAPI.as_view(), name='api_savings_posting'), 7 | ] -------------------------------------------------------------------------------- /backend/static/src/scss/core/_progress.scss: -------------------------------------------------------------------------------- 1 | .progress-xs { 2 | height: 4px; 3 | } 4 | 5 | .progress-sm { 6 | height: 8px; 7 | } 8 | 9 | // White progress bar 10 | .progress-white { 11 | background-color: rgba(255,255,255,.2) !important; 12 | .progress-bar { 13 | background-color: #fff; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | web: 5 | container_name: 'korjo-frontend' 6 | build: . 7 | command: 'npm run dev' 8 | volumes: 9 | - .:/app 10 | ports: 11 | - '3000:3000' 12 | env_file: 13 | - .env 14 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_tables.scss: -------------------------------------------------------------------------------- 1 | .table-outline { 2 | border: 1px solid $table-border-color; 3 | 4 | td { 5 | vertical-align: middle; 6 | } 7 | } 8 | 9 | .table-align-middle { 10 | 11 | td { 12 | vertical-align: middle; 13 | } 14 | } 15 | 16 | .table-clear { 17 | td { 18 | border: 0; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | 3 | # Set the working directory to /app 4 | WORKDIR /app 5 | 6 | # Copy the package.json and package-lock.json files to the container 7 | COPY package*.json ./ 8 | 9 | # Install app dependencies 10 | RUN npm install 11 | 12 | # Copy the rest of the application code to the container 13 | COPY . . 14 | -------------------------------------------------------------------------------- /frontend/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Head, Html, Main, NextScript } from 'next/document'; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim-buster 2 | 3 | # set environment variables 4 | ENV PYTHONDONTWRITEBYTECODE 1 5 | ENV PYTHONUNBUFFERED 1 6 | 7 | # set working directory 8 | WORKDIR /app 9 | 10 | # install dependencies 11 | RUN pip install --upgrade pip 12 | COPY ./requirements.txt . 13 | RUN pip install -r requirements.txt 14 | 15 | # copy project 16 | COPY . . 17 | -------------------------------------------------------------------------------- /frontend/src/schema/AllModalSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const DisbursementSchema = z.object({ 4 | loan_amount: z.string().trim().min(1, { message: 'please insert loan disbursement amount' }), 5 | date: z.string(), 6 | installment_count: z.string().trim().min(1, { message: 'please insert installment counting' }), 7 | }); 8 | export type DisbursementType = z.infer; 9 | -------------------------------------------------------------------------------- /backend/korjo_soft/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | 3 | class IsBranchOwner(BasePermission): 4 | message = 'You must be the Branch Owner' 5 | 6 | def has_permission(self, request, view): 7 | return request.user.is_authenticated and request.user.role=='BO' 8 | 9 | def has_object_permission(self, request, view, obj): 10 | return obj.branch__user == request.user -------------------------------------------------------------------------------- /frontend/src/Layouts/DashboardLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import DashboardNavbar from '@/components/DashboardNavbar'; 2 | 3 | const DashboardLayout: React.FC = ({ children, className = '' }) => { 4 | return ( 5 |
6 | 7 | {children} 8 |
9 | ); 10 | }; 11 | 12 | export default DashboardLayout; 13 | -------------------------------------------------------------------------------- /frontend/src/utils/removeNullOrEmpty.ts: -------------------------------------------------------------------------------- 1 | const removeNullOrEmpty = (obj: T) => { 2 | Object.keys(obj).forEach( 3 | (key) => 4 | (obj[key] && typeof obj[key] === 'object' && removeNullOrEmpty(obj[key])) || 5 | ((obj[key] === undefined || obj[key] === null || obj[key] === '') && delete obj[key]) 6 | ); 7 | return obj; 8 | }; 9 | 10 | export default removeNullOrEmpty; 11 | -------------------------------------------------------------------------------- /backend/organization/migrations/0002_remove_branch_thana.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.2 on 2023-07-24 22:21 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("organization", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="branch", 14 | name="thana", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /frontend/src/utils/zodSchemaParse.ts: -------------------------------------------------------------------------------- 1 | import removeNullOrEmpty from '@/utils/removeNullOrEmpty'; 2 | import { Resolver, zodResolver } from '@hookform/resolvers/zod'; 3 | 4 | const zodSchemaResolver: Resolver = (schema, schemaOptions, resolverOptions = {}) => { 5 | return (value, _, option) => 6 | zodResolver(schema, schemaOptions, resolverOptions)(removeNullOrEmpty(value), _, option); 7 | }; 8 | 9 | export default zodSchemaResolver; 10 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_grid.scss: -------------------------------------------------------------------------------- 1 | .row.row-equal { 2 | padding-right: ($grid-gutter-width / 4); 3 | padding-left: ($grid-gutter-width / 4); 4 | margin-right: ($grid-gutter-width / -2); 5 | margin-left: ($grid-gutter-width / -2); 6 | 7 | [class*="col-"] { 8 | padding-right: ($grid-gutter-width / 4); 9 | padding-left: ($grid-gutter-width / 4); 10 | } 11 | } 12 | 13 | .main .container-fluid { 14 | padding: 0 30px; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/next.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const nextTranslate = require('next-translate-plugin'); 3 | // @ts-check 4 | 5 | /** @type {import('next').NextConfig} */ 6 | 7 | const nextConfig = { 8 | reactStrictMode: true, 9 | typescript: { 10 | ignoreBuildErrors: true, 11 | }, 12 | eslint: { 13 | ignoreDuringBuilds: true, 14 | }, 15 | } 16 | 17 | module.exports = nextTranslate(nextConfig) 18 | -------------------------------------------------------------------------------- /frontend/src/utils/getWeekNoOfCurrentMonth.ts: -------------------------------------------------------------------------------- 1 | function getWeekNumberOfCurrentMonth(date = new Date()) { 2 | const year = date.getFullYear(); 3 | const month = date.getMonth(); 4 | const firstDayOfMonth = new Date(year, month, 1); 5 | const daysOffset = firstDayOfMonth.getDay(); 6 | const weekNumber = Math.floor((date.getDate() + daysOffset) / 7); 7 | return weekNumber; 8 | } 9 | 10 | export default getWeekNumberOfCurrentMonth; 11 | -------------------------------------------------------------------------------- /frontend/src/utils/messages.ts: -------------------------------------------------------------------------------- 1 | import Swal, { SweetAlertIcon } from 'sweetalert2'; 2 | 3 | //success messages 4 | export const showNotification = (message: string, title: string, icon: SweetAlertIcon) => { 5 | Swal.fire({ 6 | position: 'center', 7 | title: title, 8 | text: message, 9 | icon: icon, 10 | // confirmButtonText: 'OK', 11 | showConfirmButton: false, 12 | timer: 3000, 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /frontend/src/Layouts/BaseLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import Footer from '@/components/Footer'; 2 | import Navbar from '@/components/Navbar'; 3 | 4 | const BaseLayout: React.FC = ({ children, className = '' }) => { 5 | return ( 6 |
7 | 8 | {children} 9 |
10 |
11 | ); 12 | }; 13 | 14 | export default BaseLayout; 15 | -------------------------------------------------------------------------------- /frontend/src/types/memberSaving.type.ts: -------------------------------------------------------------------------------- 1 | export type MemberSavingsType = { 2 | sl: number; 3 | member_id: number; 4 | member_name: string; 5 | guardian_name: string; 6 | balance: number; 7 | week1: number; 8 | week2: number; 9 | week3: number; 10 | week4: number; 11 | }; 12 | export type FetchedMemberSavingsType = { 13 | count: number; 14 | next: string; 15 | previous: string; 16 | results: MemberSavingsType[]; 17 | }; -------------------------------------------------------------------------------- /frontend/src/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | function getYear() { 2 | return new Date().getFullYear(); 3 | } 4 | 5 | const Footer = () => { 6 | return ( 7 |
8 |
9 |

© {getYear()} Qard al-Hasan. All rights reserved.

10 |
11 |
12 | ); 13 | }; 14 | 15 | export default Footer; 16 | -------------------------------------------------------------------------------- /frontend/src/utils/isRouterPathnameMatched.ts: -------------------------------------------------------------------------------- 1 | import isStringMatched from './isStringMatched'; 2 | 3 | const isRouterPathnameMatched = (pathname: string, routerPath: string, exact: boolean) => { 4 | let isMatch = false; 5 | 6 | if (exact) { 7 | isMatch = pathname === routerPath; 8 | } else { 9 | isMatch = isStringMatched(routerPath, pathname); 10 | } 11 | 12 | return isMatch; 13 | }; 14 | 15 | export default isRouterPathnameMatched; 16 | -------------------------------------------------------------------------------- /backend/korjo_soft/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for korjo_soft project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'korjo_soft.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /backend/korjo_soft/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for korjo_soft project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'korjo_soft.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_animate.scss: -------------------------------------------------------------------------------- 1 | // scss-lint:disable all 2 | .animated { 3 | animation-duration: 1s; 4 | // animation-fill-mode: both; 5 | } 6 | 7 | .animated.infinite { 8 | animation-iteration-count: infinite; 9 | } 10 | 11 | .animated.hinge { 12 | animation-duration: 2s; 13 | } 14 | 15 | @keyframes fadeIn { 16 | from { 17 | opacity: 0; 18 | } 19 | 20 | to { 21 | opacity: 1; 22 | } 23 | } 24 | 25 | .fadeIn { 26 | animation-name: fadeIn; 27 | } 28 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@typescript-eslint"], 3 | "extends": [ 4 | "prettier", 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "next/core-web-vitals", 8 | "next" 9 | ], 10 | "rules": { 11 | "no-console": "error", 12 | "@typescript-eslint/no-unused-vars": "error", 13 | "react/react-in-jsx-scope": "off", 14 | "@next/next/no-img-element": "off" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/report/migrations/0003_remove_cihcalculation_organization.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2024-08-27 04:04 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("report", "0002_rename_cache_in_hand_cihcalculation_cash_in_hand"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="cihcalculation", 14 | name="organization", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/report/migrations/0002_rename_cache_in_hand_cihcalculation_cash_in_hand.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.2 on 2023-07-28 01:07 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("report", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RenameField( 13 | model_name="cihcalculation", 14 | old_name="cache_in_hand", 15 | new_name="cash_in_hand", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /frontend/src/schema/TeamSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const depositSchema = z.object({ 4 | amount: z.string().trim().min(1, { message: 'Please insert your amount.' }), 5 | // date: z.string().min(1, { message: 'Please insert your Date.' }), 6 | }); 7 | export const depositFormSubmitSchema = z.object({ 8 | member: z.number(), 9 | amount: z.number(), 10 | date: z.string().min(1, { message: 'Please insert your Date.' }), 11 | }); 12 | 13 | export type DepositType = z.infer; 14 | -------------------------------------------------------------------------------- /frontend/src/types/incomeExpense.type.ts: -------------------------------------------------------------------------------- 1 | export type BranchIncomeExpenseType = { 2 | id: number; 3 | amount: number; 4 | date: string; 5 | category: number; 6 | summary: string; 7 | }; 8 | 9 | export type BranchIncomeExpenseListType = { 10 | count: number; 11 | next: null | string; 12 | previous: null | string; 13 | results: Array; 14 | }; 15 | export type BranchAddExpensePayloadType = { 16 | amount: number; 17 | date: string; 18 | category: number; 19 | summary: string; 20 | }; 21 | -------------------------------------------------------------------------------- /backend/peoples/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin.sites import AlreadyRegistered 2 | from django.contrib import admin 3 | from django.apps import apps 4 | 5 | from peoples.models import Staff, Member 6 | 7 | 8 | @admin.register(Staff) 9 | class StaffAdmin(admin.ModelAdmin): 10 | list_display = ['id', 'name',] 11 | list_display_links = ['name'] 12 | ordering = ['id'] 13 | 14 | 15 | @admin.register(Member) 16 | class MemberAdmin(admin.ModelAdmin): 17 | list_display = ('id', 'name', 'guardian_name', 'serial_number', 'team') 18 | search_fields = ('name',) -------------------------------------------------------------------------------- /backend/transaction/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import * 3 | 4 | urlpatterns =[ 5 | path('', dashboard, name="dashboard"), 6 | path('deposits/', deposit_list, name="deposit_list"), 7 | path('loan/list/', loan_list, name="loan_list"), 8 | path('deposit-posting/', DepositPostingView.as_view(), name="deposit_posting"), 9 | path('loans/create/', LoanDisbursementView.as_view(), name='loan_disbursement'), 10 | path('loans/installment/', installment_posting, name='installment_posting'), 11 | 12 | ] -------------------------------------------------------------------------------- /frontend/src/types/memberInstallment.type.ts: -------------------------------------------------------------------------------- 1 | export type MemberInstallmentType = { 2 | sl: number; 3 | member_id: number; 4 | loan_id: number; 5 | loan_balance: number; 6 | loan_amount: number; 7 | member_name: string; 8 | guardian_name: string; 9 | balance: number; 10 | week1: number; 11 | week2: number; 12 | week3: number; 13 | week4: number; 14 | }; 15 | export type FetchedMemberInstallmentsType = { 16 | count: number; 17 | next: string; 18 | previous: string; 19 | results: MemberInstallmentType[]; 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | yarn.lock 31 | .env 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | -------------------------------------------------------------------------------- /backend/static/src/scss/style.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * CoreUI - Open Source Bootstrap Admin Template 3 | * @version v1.0.6 4 | * @link http://coreui.io 5 | * Copyright (c) 2017 creativeLabs Łukasz Holeczek 6 | * @license MIT 7 | */ 8 | 9 | // Override Boostrap variables 10 | // @import "bootstrap-variables"; 11 | @import "core-variables"; 12 | 13 | // Import Bootstrap source files 14 | @import "node_modules/bootstrap/scss/bootstrap"; 15 | 16 | // Override core variables 17 | // @import "core-variables"; 18 | 19 | // Import core styles 20 | @import "core/core"; 21 | 22 | // Custom styles 23 | @import "custom"; 24 | -------------------------------------------------------------------------------- /frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/icons/FilterIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | export type Icon = SVGAttributes; 3 | 4 | const FilterIcon: FC = () => { 5 | return ( 6 | 7 | 14 | 15 | ); 16 | }; 17 | 18 | export default FilterIcon; 19 | -------------------------------------------------------------------------------- /frontend/src/icons/PlusIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | 3 | export type Icon = SVGAttributes; 4 | const PlusIcon: FC = () => { 5 | return ( 6 | 7 | 14 | 15 | ); 16 | }; 17 | 18 | export default PlusIcon; 19 | -------------------------------------------------------------------------------- /backend/journal/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Ledger, LedgerType, GeneralJournal 3 | 4 | 5 | class JournalAdmin(admin.ModelAdmin): 6 | list_display = ('date', 'member', 'accounts', 'debit', 'credit') 7 | search_fields = ('member__name', ) 8 | 9 | 10 | class LedgerAdmin(admin.ModelAdmin): 11 | list_display = ('code', 'name', 'ledger_type') 12 | 13 | 14 | class LedgerTypeAdmin(admin.ModelAdmin): 15 | list_display = ('code', 'name',) 16 | 17 | 18 | admin.site.register(GeneralJournal, JournalAdmin) 19 | admin.site.register(Ledger, LedgerAdmin) 20 | admin.site.register(LedgerType, LedgerTypeAdmin) -------------------------------------------------------------------------------- /backend/peoples/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework import routers 3 | from peoples.views import ( 4 | MemberListCreateView, 5 | MemberDetailsView, 6 | MemberSavingLoanInfo 7 | ) 8 | 9 | 10 | # peoples router config 11 | peoples_router = routers.DefaultRouter() 12 | 13 | 14 | urlpatterns = [ 15 | path("members/", MemberListCreateView.as_view(), name="list_create"), 16 | path("members//", MemberDetailsView.as_view(), name="details"), 17 | path("members//saving-loan-info/", MemberSavingLoanInfo.as_view(), name="saving_loan_info"), 18 | ] 19 | 20 | urlpatterns += peoples_router.urls -------------------------------------------------------------------------------- /backend/templates/auth/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Login 7 | 8 | 9 |

Login

10 |
11 | {% csrf_token %} 12 | {{ form.as_p }} 13 | 14 |
15 | {% if messages %} 16 |
    17 | {% for message in messages %} 18 |
  • {{ message }}
  • 19 | {% endfor %} 20 |
21 | {% endif %} 22 | 23 | 24 | -------------------------------------------------------------------------------- /backend/templates/base/sidebar.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_typography.scss: -------------------------------------------------------------------------------- 1 | body { 2 | -moz-osx-font-smoothing: grayscale; 3 | -webkit-font-smoothing: antialiased; 4 | } 5 | 6 | .font-xs { 7 | font-size: .75rem !important; 8 | } 9 | 10 | .font-sm { 11 | font-size: .85rem !important; 12 | } 13 | 14 | .font-lg { 15 | font-size: 1rem !important; 16 | } 17 | 18 | .font-xl { 19 | font-size: 1.25rem !important; 20 | } 21 | 22 | .font-2xl { 23 | font-size: 1.5rem !important; 24 | } 25 | 26 | .font-3xl { 27 | font-size: 1.75rem !important; 28 | } 29 | 30 | .font-4xl { 31 | font-size: 2rem !important; 32 | } 33 | 34 | .font-5xl { 35 | font-size: 2.5rem !important; 36 | } 37 | -------------------------------------------------------------------------------- /backend/.dockerignore: -------------------------------------------------------------------------------- 1 | # virtualenvs 2 | .venv 3 | venv 4 | 5 | # Ignore Python bytecodes and caches 6 | __pycache__ 7 | *.pyc 8 | *.pyo 9 | *.pyd 10 | 11 | # Ignore Django generated files 12 | *.log 13 | *.pot 14 | *.pyc 15 | *.pyo 16 | *.swp 17 | *.sqlite3 18 | *.pyc 19 | *.pyo 20 | *.coverage 21 | 22 | # Ignore local development files 23 | *.env 24 | *.env.* 25 | *.sqlite 26 | 27 | # Ignore test and documentation directories 28 | /docs 29 | /tests 30 | .coverage 31 | 32 | # Ignore node modules and dependencies 33 | node_modules 34 | package-lock.json 35 | yarn.lock 36 | 37 | # Ignore other unnecessary files 38 | .DS_Store 39 | .idea 40 | .vscode 41 | *.bak 42 | *.tmp 43 | *.swp 44 | -------------------------------------------------------------------------------- /backend/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | web: 5 | container_name: "korjo-backend" 6 | build: . 7 | command: > 8 | bash -c "python manage.py migrate && 9 | python manage.py prepare_org && 10 | python manage.py runserver 0.0.0.0:8000 11 | " 12 | volumes: 13 | - .:/app 14 | - static_data:/app/static 15 | - db_data:/app/db 16 | - media_data:/app/media 17 | ports: 18 | - "8000:8000" 19 | env_file: 20 | - .env 21 | environment: 22 | - DATABASE_URL=sqlite:////app/db/db.sqlite3 23 | 24 | volumes: 25 | static_data: 26 | db_data: 27 | media_data: 28 | -------------------------------------------------------------------------------- /backend/transaction/migrations/0002_transactioncategory_category_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.2 on 2023-09-01 05:49 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("transaction", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="transactioncategory", 14 | name="category_type", 15 | field=models.CharField( 16 | choices=[("income", "Income"), ("expense", "Expense")], 17 | default="income", 18 | max_length=10, 19 | ), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /frontend/src/icons/NextIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | export type Icon = SVGAttributes; 3 | 4 | const NextIcon: FC = () => { 5 | return ( 6 | 7 | 14 | 15 | ); 16 | }; 17 | 18 | export default NextIcon; 19 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_nav.scss: -------------------------------------------------------------------------------- 1 | .nav-tabs { 2 | .nav-link { 3 | color: $gray-600; 4 | &.active { 5 | color: $gray-800; 6 | background: #fff; 7 | border-color: $border-color; 8 | border-bottom-color: #fff; 9 | &:focus { 10 | background: #fff; 11 | border-color: $border-color; 12 | border-bottom-color: #fff; 13 | } 14 | } 15 | } 16 | } 17 | 18 | .tab-content { 19 | margin-top: -1px; 20 | background: #fff; 21 | border: 1px solid $border-color; 22 | .tab-pane { 23 | padding: $spacer; 24 | } 25 | } 26 | 27 | .card-block { 28 | .tab-content { 29 | margin-top: 0; 30 | border: 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/icons/PreviousIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | export type Icon = SVGAttributes; 3 | 4 | const PreviousIcon: FC = () => { 5 | return ( 6 | 7 | 14 | 15 | ); 16 | }; 17 | 18 | export default PreviousIcon; 19 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_breadcrumb-menu.scss: -------------------------------------------------------------------------------- 1 | .breadcrumb-menu { 2 | margin-left: auto; 3 | 4 | &::before { 5 | display: none; 6 | } 7 | 8 | .btn-group { 9 | vertical-align: top; 10 | } 11 | 12 | .btn { 13 | padding: 0 $input-btn-padding-x; 14 | color: $text-muted; 15 | vertical-align: top; 16 | border: 0; 17 | 18 | &:hover, &.active { 19 | color: $body-color; 20 | background: transparent; 21 | } 22 | } 23 | 24 | .open { 25 | .btn { 26 | color: $body-color; 27 | background: transparent; 28 | } 29 | } 30 | 31 | .dropdown-menu { 32 | min-width: 180px; 33 | line-height: $line-height-base; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_temp.scss: -------------------------------------------------------------------------------- 1 | .pagination-datatables, .pagination { 2 | li { 3 | @extend .page-item; 4 | 5 | a { 6 | @extend .page-link; 7 | } 8 | } 9 | } 10 | 11 | .label-pill { 12 | border-radius: 1rem !important; 13 | } 14 | 15 | // temp fix for Vue & React 16 | 17 | // Open state for the dropdown 18 | .open, .show { 19 | // Remove the outline when :focus is triggered 20 | > a { 21 | outline: 0; 22 | } 23 | } 24 | 25 | // navbar dropdown fix 26 | .navbar .dropdown-toggle { 27 | @extend .nav-link; 28 | 29 | .img-avatar { 30 | height: $navbar-height - 20px; 31 | margin: 0 10px; 32 | } 33 | } 34 | 35 | .card-block { 36 | @extend .card-body; 37 | } 38 | -------------------------------------------------------------------------------- /backend/static/src/vendors/css/chart.min.css: -------------------------------------------------------------------------------- 1 | .bar-legend,.chart-legend,.doughnut-legend,.line-legend,.pie-legend,.polararea-legend,.radar-legend{list-style-type:none;margin-top:5px;text-align:center;-webkit-padding-start:0;-moz-padding-start:0;padding-left:0}.bar-legend li,.chart-legend li,.doughnut-legend li,.line-legend li,.pie-legend li,.polararea-legend li,.radar-legend li{display:inline-block;white-space:nowrap;position:relative;margin-bottom:4px;padding:2px 8px 2px 28px;font-size:smaller;cursor:default}.bar-legend li span,.chart-legend li span,.doughnut-legend li span,.line-legend li span,.pie-legend li span,.polararea-legend li span,.radar-legend li span{display:block;position:absolute;left:0;top:0;width:20px;height:20px} -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | moment-timezone@^0.5.43: 6 | version "0.5.43" 7 | resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.43.tgz#3dd7f3d0c67f78c23cd1906b9b2137a09b3c4790" 8 | integrity sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ== 9 | dependencies: 10 | moment "^2.29.4" 11 | 12 | moment@^2.29.4: 13 | version "2.29.4" 14 | resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" 15 | integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== 16 | -------------------------------------------------------------------------------- /frontend/src/utils/http.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { getSession } from 'next-auth/react'; 3 | import { env } from '../env.mjs'; 4 | 5 | const http = axios.create({ 6 | baseURL: env.NEXT_PUBLIC_BACKEND_API_URL, 7 | 8 | headers: { 9 | Accept: 'application/json', 10 | 'Content-Type': 'application/json', 11 | }, 12 | }); 13 | 14 | http.interceptors.request.use(async (request) => { 15 | const session = await getSession(); 16 | 17 | if (session) { 18 | request.headers.Authorization = `Bearer ${session?.user?.accessToken}`; 19 | } 20 | return request; 21 | }); 22 | 23 | export default http; 24 | 25 | export const BASE_URL = process.env.NEXT_PUBLIC_BACKEND_API_URL; 26 | -------------------------------------------------------------------------------- /frontend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": "explicit" 4 | }, 5 | "editor.formatOnSave": true, // Tell VSCode to format files on save 6 | "editor.defaultFormatter": "esbenp.prettier-vscode", 7 | "[prisma]": { 8 | "editor.defaultFormatter": "Prisma.prisma" 9 | }, // Tell VSCode to use Prettier as default file formatter, 10 | "tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]], 11 | "typescript.enablePromptUseWorkspaceTsdk": true, 12 | "typescript.tsdk": "node_modules/typescript/lib", 13 | "[typescriptreact]": { 14 | "editor.defaultFormatter": "vscode.typescript-language-features" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/organization/tests.py: -------------------------------------------------------------------------------- 1 | from rest_framework import status 2 | from rest_framework.test import APITestCase 3 | 4 | 5 | class StaffTestCase(APITestCase): 6 | """ Staff test cases """ 7 | 8 | # List (GET): /api/v1/organization/staffs/ 9 | def test_staff_list(self): 10 | response = self.client.get('/api/v1/organization/staffs/') 11 | self.assertEqual(response.status_code, status.HTTP_200_OK) 12 | 13 | 14 | class BranchTestCase(APITestCase): 15 | """ Branch test cases """ 16 | 17 | # List (GET): /api/v1/organization/branches/ 18 | def test_branch_list(self): 19 | response = self.client.get('/api/v1/organization/branches/') 20 | self.assertEqual(response.status_code, status.HTTP_200_OK) 21 | 22 | -------------------------------------------------------------------------------- /backend/transaction/migrations/0003_remove_generaltransaction_organization_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2024-08-27 04:04 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("transaction", "0002_transactioncategory_category_type"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="generaltransaction", 14 | name="organization", 15 | ), 16 | migrations.RemoveField( 17 | model_name="loan", 18 | name="organization", 19 | ), 20 | migrations.RemoveField( 21 | model_name="savings", 22 | name="organization", 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'korjo_soft.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /backend/templates/base/breadcrumb.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | -------------------------------------------------------------------------------- /backend/organization/managers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import BaseUserManager 2 | 3 | 4 | class UserManager(BaseUserManager): 5 | def create_user(self, username, password=None): 6 | if not username: 7 | raise ValueError("Users must have a username") 8 | 9 | user = self.model( 10 | username=username, 11 | ) 12 | 13 | user.set_password(password) 14 | user.save(using=self._db) 15 | return user 16 | 17 | def create_superuser(self, username, password): 18 | user = self.create_user( 19 | username=username, 20 | password=password, 21 | ) 22 | user.is_staff = True 23 | user.is_superuser = True 24 | user.save(using=self._db) 25 | return user 26 | -------------------------------------------------------------------------------- /frontend/src/icons/SearchIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | export type Icon = SVGAttributes; 3 | const SearchIcon: FC = () => { 4 | return ( 5 | 6 | 13 | 14 | ); 15 | }; 16 | 17 | export default SearchIcon; 18 | -------------------------------------------------------------------------------- /backend/organization/paginations/paginations.py: -------------------------------------------------------------------------------- 1 | from rest_framework.pagination import PageNumberPagination 2 | 3 | 4 | class CommonPageNumberPagination(PageNumberPagination): 5 | """ common pagination """ 6 | 7 | page_size = 5 # change this value based on needs 8 | page_size_query_param = 'page_size' 9 | 10 | def get_next_link(self): 11 | if not self.page.has_next(): 12 | return None 13 | url = self.request.build_absolute_uri() 14 | page_number = self.page.next_page_number() 15 | return page_number 16 | 17 | def get_previous_link(self): 18 | if not self.page.has_previous(): 19 | return None 20 | url = self.request.build_absolute_uri() 21 | page_number = self.page.previous_page_number() 22 | return page_number 23 | 24 | -------------------------------------------------------------------------------- /backend/organization/routes.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework.routers import DefaultRouter 3 | from organization.api import ( 4 | TeamCreateListApiView, 5 | TeamRetriveUpdateDestroyView, 6 | StaffReadOnlyModelViewSet, 7 | BranchDetailView, 8 | ) 9 | 10 | # router config 11 | router = DefaultRouter() 12 | 13 | # router api verbs 14 | router.register(r"staffs", StaffReadOnlyModelViewSet, basename="staffs") 15 | 16 | urlpatterns = [ 17 | path("teams/", TeamCreateListApiView.as_view(), name="teams"), 18 | path( 19 | "teams//", 20 | TeamRetriveUpdateDestroyView.as_view(), 21 | name="team_re_up_delete", 22 | ), 23 | path("branches//", BranchDetailView.as_view(), name="branch_detail"), 24 | 25 | ] 26 | 27 | urlpatterns += router.urls 28 | -------------------------------------------------------------------------------- /frontend/src/modules/team/stores/useMemberSavingsStore.ts: -------------------------------------------------------------------------------- 1 | import create from 'zustand'; 2 | import { MemberSavingsType } from '../../../types/memberSaving.type'; 3 | 4 | interface IMemberSavingsStore { 5 | memberTransactions: MemberSavingsType[]; 6 | selectedMember: MemberSavingsType | null; 7 | actions: { 8 | setTransactions: (newSavings: MemberSavingsType[]) => void; 9 | setSelectedMember: (data: MemberSavingsType) => void; 10 | }; 11 | } 12 | 13 | export const useMemberSavingsStore = create((set) => ({ 14 | memberTransactions: [], 15 | selectedMember: null, 16 | actions: { 17 | setTransactions: (transactions) => set({ memberTransactions: transactions }), 18 | setSelectedMember: (data:MemberSavingsType) => set({ selectedMember: data }), 19 | }, 20 | })); 21 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/utilities/_display.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Utilities for common `display` values 3 | // 4 | 5 | @each $breakpoint in map-keys($grid-breakpoints) { 6 | @include media-breakpoint-down($breakpoint) { 7 | $infix: breakpoint-infix($breakpoint, $grid-breakpoints); 8 | 9 | .d#{$infix}-down-none { display: none !important; } 10 | // .d#{$infix}-inline { display: inline !important; } 11 | // .d#{$infix}-inline-block { display: inline-block !important; } 12 | // .d#{$infix}-block { display: block !important; } 13 | // .d#{$infix}-table { display: table !important; } 14 | // .d#{$infix}-table-cell { display: table-cell !important; } 15 | // .d#{$infix}-flex { display: flex !important; } 16 | // .d#{$infix}-inline-flex { display: inline-flex !important; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /backend/templates/base/js.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /backend/templates/base/header.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Circle Fintech 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/src/modules/team/stores/useMemberInstallmentsStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | import { MemberInstallmentType } from '../../../types/memberInstallment.type'; 3 | 4 | interface IMemberInstallmentsStore { 5 | memberTransactions: MemberInstallmentType[]; 6 | selectedMember: MemberInstallmentType | null; 7 | actions: { 8 | setTransactions: (newInstallments: MemberInstallmentType[]) => void; 9 | setSelectedMember: (data: MemberInstallmentType) => void; 10 | }; 11 | } 12 | 13 | export const useMemberInstallmentsStore = create((set) => ({ 14 | memberTransactions: [], 15 | selectedMember: null, 16 | actions: { 17 | setTransactions: (transactions) => set({ memberTransactions: transactions }), 18 | setSelectedMember: (data: MemberInstallmentType) => set({ selectedMember: data }), 19 | }, 20 | })); 21 | -------------------------------------------------------------------------------- /backend/journal/migrations/0002_alter_ledger_code_alter_ledgertype_code_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2024-08-25 05:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("peoples", "0001_initial"), 9 | ("journal", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="ledger", 15 | name="code", 16 | field=models.CharField(max_length=10, unique=True), 17 | ), 18 | migrations.AlterField( 19 | model_name="ledgertype", 20 | name="code", 21 | field=models.CharField(max_length=10, unique=True), 22 | ), 23 | migrations.AlterUniqueTogether( 24 | name="generaljournal", 25 | unique_together={("date", "accounts", "member")}, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /frontend/src/icons/ExpenseIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | export type Icon = SVGAttributes; 3 | 4 | const ExpenseIcon: FC = () => { 5 | return ( 6 | 7 | 14 | 15 | ); 16 | }; 17 | 18 | export default ExpenseIcon; 19 | -------------------------------------------------------------------------------- /frontend/src/components/CommonBreadCrumb/index.tsx: -------------------------------------------------------------------------------- 1 | import { Breadcrumb, BreadcrumbItem, BreadcrumbLink } from '@chakra-ui/react'; 2 | 3 | export type SingleBreadCrumbItemType = { label: string; href?: string; isCurrentPage?: boolean }; 4 | interface ICommonBreadCrumbProps { 5 | items: Array; 6 | } 7 | 8 | const CommonBreadCrumb: React.FC = ({ items }) => { 9 | return ( 10 | 11 | {items.map((item: SingleBreadCrumbItemType) => ( 12 | 13 | 14 | {item?.label} 15 | 16 | 17 | ))} 18 | 19 | ); 20 | }; 21 | 22 | export default CommonBreadCrumb; 23 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/core.scss: -------------------------------------------------------------------------------- 1 | // Import core styles 2 | @import "variables"; 3 | @import "mixins"; 4 | 5 | // Additional typography 6 | @import "typography"; 7 | 8 | // Animations 9 | @import "animate"; 10 | 11 | // Components 12 | @import "aside"; 13 | @import "avatars"; 14 | @import "badge"; 15 | @import "breadcrumb-menu"; 16 | @import "breadcrumb"; 17 | @import "buttons"; 18 | @import "callout"; 19 | @import "card"; 20 | @import "charts"; 21 | @import "dropdown"; 22 | @import "footer"; 23 | @import "grid"; 24 | @import "input-group"; 25 | @import "loading"; 26 | @import "modal"; 27 | @import "nav"; 28 | @import "navbar"; 29 | @import "progress"; 30 | @import "sidebar"; 31 | @import "switches"; 32 | @import "tables"; 33 | @import "widgets"; 34 | 35 | // Layout Options 36 | @import "layout"; 37 | 38 | @import "others"; 39 | 40 | // Utility classes 41 | @import "utilities"; 42 | 43 | // Temporary fixes 44 | @import "temp"; 45 | 46 | // Right-to-left 47 | @import "rtl"; 48 | -------------------------------------------------------------------------------- /frontend/src/icons/LoanPlusIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | 3 | export type Icon = SVGAttributes; 4 | const LoanPlusIcon: FC = () => { 5 | return ( 6 | 7 | 14 | 15 | ); 16 | }; 17 | 18 | export default LoanPlusIcon; 19 | -------------------------------------------------------------------------------- /backend/accounts/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | from django.contrib.auth import authenticate, login 3 | from django.contrib import messages 4 | from .forms import LoginForm 5 | 6 | 7 | def login_view(request): 8 | if request.method == 'POST': 9 | form = LoginForm(request.POST) 10 | if form.is_valid(): 11 | username = form.cleaned_data['username'] 12 | password = form.cleaned_data['password'] 13 | user = authenticate(request, username=username, password=password) 14 | 15 | if user is not None: 16 | login(request, user) 17 | messages.success(request, 'You have successfully logged in.') 18 | return redirect('dashboard') # Redirect to the homepage or any other page 19 | else: 20 | messages.error(request, 'Invalid username or password.') 21 | else: 22 | form = LoginForm() 23 | 24 | return render(request, 'auth/login.html', {'form': form}) 25 | -------------------------------------------------------------------------------- /backend/static/src/vendors/css/chart.css: -------------------------------------------------------------------------------- 1 | .chart-legend, 2 | .bar-legend, 3 | .line-legend, 4 | .pie-legend, 5 | .radar-legend, 6 | .polararea-legend, 7 | .doughnut-legend { 8 | list-style-type: none; 9 | margin-top: 5px; 10 | text-align: center; 11 | -webkit-padding-start: 0; 12 | -moz-padding-start: 0; 13 | padding-left: 0; } 14 | 15 | .chart-legend li, 16 | .bar-legend li, 17 | .line-legend li, 18 | .pie-legend li, 19 | .radar-legend li, 20 | .polararea-legend li, 21 | .doughnut-legend li { 22 | display: inline-block; 23 | white-space: nowrap; 24 | position: relative; 25 | margin-bottom: 4px; 26 | padding: 2px 8px 2px 28px; 27 | font-size: smaller; 28 | cursor: default; } 29 | 30 | .chart-legend li span, 31 | .bar-legend li span, 32 | .line-legend li span, 33 | .pie-legend li span, 34 | .radar-legend li span, 35 | .polararea-legend li span, 36 | .doughnut-legend li span { 37 | display: block; 38 | position: absolute; 39 | left: 0; 40 | top: 0; 41 | width: 20px; 42 | height: 20px; } 43 | -------------------------------------------------------------------------------- /frontend/src/icons/LoanIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | 3 | export type Icon = SVGAttributes; 4 | const LoanIcon: FC = ({ width, height, ...rest }) => { 5 | return ( 6 | 7 | 15 | 16 | ); 17 | }; 18 | 19 | export default LoanIcon; 20 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_avatars.scss: -------------------------------------------------------------------------------- 1 | .img-avatar { 2 | border-radius: 50em; 3 | } 4 | 5 | .avatar { 6 | $width: 36px; 7 | $status-width: 10px; 8 | @include avatar($width,$status-width); 9 | } 10 | 11 | .avatar.avatar-xs { 12 | $width: 20px; 13 | $status-width: 8px; 14 | @include avatar($width,$status-width); 15 | } 16 | 17 | .avatar.avatar-sm { 18 | $width: 24px; 19 | $status-width: 8px; 20 | @include avatar($width,$status-width); 21 | } 22 | 23 | .avatar.avatar-lg { 24 | $width: 72px; 25 | $status-width: 12px; 26 | @include avatar($width,$status-width); 27 | } 28 | 29 | .avatars-stack { 30 | .avatar.avatar-xs { 31 | margin-right: -10px; 32 | } 33 | 34 | // .avatar.avatar-sm { 35 | // 36 | // } 37 | 38 | .avatar { 39 | margin-right: -15px; 40 | transition: margin-left $layout-transition-speed, margin-right $layout-transition-speed; 41 | 42 | &:hover { 43 | margin-right: 0 !important; 44 | } 45 | } 46 | 47 | // .avatar.avatar-lg { 48 | // 49 | // } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": false, 4 | "declaration": true, 5 | "declarationMap": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "inlineSources": false, 9 | "isolatedModules": true, 10 | "moduleResolution": "node", 11 | "noUnusedLocals": false, 12 | "noUnusedParameters": false, 13 | "preserveWatchOutput": true, 14 | "skipLibCheck": true, 15 | "strict": true, 16 | "lib": ["ES2020", "DOM", "DOM.Iterable", "ESNext"], 17 | "target": "ES2020", 18 | "allowJs": true, 19 | "noEmit": true, 20 | "incremental": true, 21 | "module": "esnext", 22 | "resolveJsonModule": true, 23 | "jsx": "preserve", 24 | "strictNullChecks": true, 25 | "paths": { 26 | "@/*": ["./src/*"] 27 | } 28 | }, 29 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 30 | "exclude": ["node_modules"] 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/icons/PersonPlusIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | 3 | export type Icon = SVGAttributes; 4 | 5 | const PersonPlusIcon: FC = () => { 6 | return ( 7 | 8 | 9 | 16 | 17 | ); 18 | }; 19 | 20 | export default PersonPlusIcon; 21 | -------------------------------------------------------------------------------- /backend/static/gulp-tasks/vendors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var del = require('del'); 5 | var rename = require('gulp-rename'); 6 | var runSequence = require('run-sequence'); 7 | var sass = require('gulp-sass'); 8 | var autoprefixer = require('gulp-autoprefixer'); 9 | var cssmin = require('gulp-cssmin') 10 | 11 | var paths = gulp.paths; 12 | 13 | gulp.task('compile-vendors:clean', function () { 14 | return del([ 15 | paths.src + 'vendors/css/**' 16 | ]); 17 | }); 18 | 19 | gulp.task('compile-vendors:sass', function () { 20 | return gulp.src(paths.src + 'scss/vendors/**/*.scss') 21 | .pipe(sass().on('error', sass.logError)) 22 | .pipe(autoprefixer()) 23 | .pipe(rename({dirname: ''})) 24 | .pipe(gulp.dest(paths.src + 'vendors/css/')) 25 | .pipe(cssmin()) 26 | .pipe(rename({suffix: '.min'})) 27 | .pipe(rename({dirname: ''})) 28 | .pipe(gulp.dest(paths.src + 'vendors/css/')); 29 | }); 30 | 31 | gulp.task('compile-vendors', function(callback) { 32 | runSequence('compile-vendors:clean', 'compile-vendors:sass', callback); 33 | }); 34 | -------------------------------------------------------------------------------- /frontend/src/icons/DepositIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | export type Icon = SVGAttributes; 3 | const DepositIcon: FC = () => { 4 | return ( 5 | 6 | 13 | 14 | ); 15 | }; 16 | 17 | export default DepositIcon; 18 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_callout.scss: -------------------------------------------------------------------------------- 1 | .callout { 2 | position: relative; 3 | padding: 0 $spacer; 4 | margin: $spacer 0; 5 | border: 0 solid $border-color; 6 | border-left-width: .25rem; 7 | 8 | @if $enable-rounded { 9 | border-radius: .25rem; 10 | } 11 | 12 | .chart-wrapper { 13 | position: absolute; 14 | top: 10px; 15 | left: 50%; 16 | float: right; 17 | width: 50%; 18 | } 19 | } 20 | 21 | .callout-bordered { 22 | border: 1px solid $border-color; 23 | border-left-width: .25rem; 24 | } 25 | .callout code { 26 | border-radius: .25rem; 27 | } 28 | .callout h4 { 29 | margin-top: 0; 30 | margin-bottom: .25rem; 31 | } 32 | .callout p:last-child { 33 | margin-bottom: 0; 34 | } 35 | .callout + .callout { 36 | margin-top: - .25rem; 37 | } 38 | 39 | .callout-default { 40 | border-left-color: $text-muted; 41 | 42 | h4 { 43 | color: $text-muted; 44 | } 45 | } 46 | 47 | @each $color, $value in $theme-colors { 48 | .callout-#{$color} { 49 | border-left-color: $value; 50 | 51 | h4 { 52 | color: $value; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/utilities/_borders.scss: -------------------------------------------------------------------------------- 1 | //border 2 | @each $prop, $abbrev in (border: b) { 3 | @each $size in (0,1,2) { 4 | @if $size == 0 { 5 | .#{$abbrev}-a-#{$size} { #{$prop}: 0 !important; } // a = All sides 6 | .#{$abbrev}-t-#{$size} { #{$prop}-top: 0 !important; } 7 | .#{$abbrev}-r-#{$size} { #{$prop}-right: 0 !important; } 8 | .#{$abbrev}-b-#{$size} { #{$prop}-bottom: 0 !important; } 9 | .#{$abbrev}-l-#{$size} { #{$prop}-left: 0 !important; } 10 | } @else { 11 | .#{$abbrev}-a-#{$size} { #{$prop}: $size * $border-width solid $border-color !important; } // a = All sides 12 | .#{$abbrev}-t-#{$size} { #{$prop}-top: $size * $border-width solid $border-color !important; } 13 | .#{$abbrev}-r-#{$size} { #{$prop}-right: $size * $border-width solid $border-color !important; } 14 | .#{$abbrev}-b-#{$size} { #{$prop}-bottom: $size * $border-width solid $border-color !important; } 15 | .#{$abbrev}-l-#{$size} { #{$prop}-left: $size * $border-width solid $border-color !important; } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/icons/WithdrawIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | 3 | export type Icon = SVGAttributes; 4 | const WithdrawIcon: FC = () => { 5 | return ( 6 | 7 | 14 | 15 | ); 16 | }; 17 | 18 | export default WithdrawIcon; 19 | -------------------------------------------------------------------------------- /backend/templates/org/team_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base/base.html' %} 2 | 3 | 4 | 5 | {% block content %} 6 |
7 |
8 | দলের নাম 9 |
10 |
11 | 12 | 13 | {% for team in teams %} 14 | 15 | 18 | 21 | 24 | 27 | 28 | {% endfor %} 29 |
16 | {{ team.name }} 17 | 19 | সঞ্চয় জমা 20 | 22 | কর্জ ফেরত 23 | 25 | কর্জ তালিকা 26 |
30 |
31 |
32 | 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /frontend/public/thirteen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/Navbar/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@chakra-ui/react'; 2 | import Link from 'next/link'; 3 | 4 | const Navbar = () => { 5 | return ( 6 |
7 |
8 | 9 |

Qard al-Hasan

10 | 11 | 12 |
13 | 14 | 17 | 18 | 19 | 22 | 23 |
24 |
25 |
26 | ); 27 | }; 28 | 29 | export default Navbar; 30 | -------------------------------------------------------------------------------- /backend/transaction/routes.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .dashboard_views import * 3 | 4 | from django.urls import path 5 | from . import api as views 6 | 7 | app_name = 'transaction' 8 | 9 | urlpatterns =[ 10 | path('deposit/', views.DepositView.as_view()), 11 | path('withdraw/', views.WithdrawView.as_view()), 12 | path('loan-disbursment/', views.LoanDisbursementView.as_view()), 13 | path('loan-installment/', views.LoanInstallmentView.as_view()), 14 | path('member-savings-list', views.MemberSavingsData.as_view()), 15 | path('member-installment-list', views.MemberLoanData.as_view()), 16 | path('income/', views.IncomeTransactionListCreate.as_view(), name='income_create_list'), 17 | path('income//', views.IncomeTransactionDetailUpdateDelete.as_view(), name='income_re_up_del'), 18 | path('expense/', views.ExpenseTransactionListCreate.as_view(), name='expense_create_list'), 19 | path('expense//', views.ExpenseTransactionDetailUpdateDelete.as_view(), name='expense_re_up_del'), 20 | path('transaction-category-list/', views.TransactionCategoryList.as_view(), name='transaction_category_list'), 21 | ] 22 | -------------------------------------------------------------------------------- /.github/workflows/auto-assign.yml.yml: -------------------------------------------------------------------------------- 1 | name: Auto-assign issue to commenter 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | jobs: 8 | assign: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Get comment body 12 | id: comment_body 13 | run: echo "::set-output name=body::${{ github.event.comment.body }}" 14 | 15 | - name: Check if comment contains "!assign me" 16 | id: check_comment 17 | run: | 18 | if [[ "${{ steps.comment_body.outputs.body }}" == *"!assign me"* ]]; then 19 | echo "::set-output name=should_assign::true" 20 | else 21 | echo "::set-output name=should_assign::false" 22 | fi 23 | 24 | - name: Assign issue to commenter 25 | if: steps.check_comment.outputs.should_assign == 'true' 26 | run: | 27 | gh auth login --with-token <<<"${{ secrets.GITHUB_TOKEN }}" 28 | gh api "/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees" \ 29 | -H "Accept: application/vnd.github.v3+json" \ 30 | --input <(echo "{ \"assignees\": [\"${{ github.event.comment.user.login }}\"] }") 31 | -------------------------------------------------------------------------------- /backend/static/src/scss/vendors/chart.js/chart.scss: -------------------------------------------------------------------------------- 1 | // Import variables 2 | @import '../variables'; 3 | 4 | .chart-legend, 5 | .bar-legend, 6 | .line-legend, 7 | .pie-legend, 8 | .radar-legend, 9 | .polararea-legend, 10 | .doughnut-legend { 11 | list-style-type: none; 12 | margin-top: 5px; 13 | text-align: center; 14 | -webkit-padding-start: 0; 15 | -moz-padding-start: 0; 16 | padding-left: 0; 17 | } 18 | .chart-legend li, 19 | .bar-legend li, 20 | .line-legend li, 21 | .pie-legend li, 22 | .radar-legend li, 23 | .polararea-legend li, 24 | .doughnut-legend li { 25 | display: inline-block; 26 | white-space: nowrap; 27 | position: relative; 28 | margin-bottom: 4px; 29 | @include border-radius($border-radius); 30 | padding: 2px 8px 2px 28px; 31 | font-size: smaller; 32 | cursor: default; 33 | } 34 | .chart-legend li span, 35 | .bar-legend li span, 36 | .line-legend li span, 37 | .pie-legend li span, 38 | .radar-legend li span, 39 | .polararea-legend li span, 40 | .doughnut-legend li span { 41 | display: block; 42 | position: absolute; 43 | left: 0; 44 | top: 0; 45 | width: 20px; 46 | height: 20px; 47 | @include border-radius($border-radius); 48 | } 49 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 23.3.0 4 | hooks: 5 | - id: black 6 | args: [--safe, --line-length=88, --target-version=py310] 7 | 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v4.4.0 10 | hooks: 11 | - id: trailing-whitespace 12 | - id: end-of-file-fixer 13 | - id: check-added-large-files 14 | - id: debug-statements 15 | language_version: python3 16 | 17 | - repo: https://github.com/PyCQA/flake8 18 | rev: 6.0.0 19 | hooks: 20 | - id: flake8 21 | args: [--max-line-length=100] 22 | language_version: python3 23 | 24 | # - repo: https://github.com/asottile/reorder_python_imports 25 | # rev: v3.9.0 26 | # hooks: 27 | # - id: reorder-python-imports 28 | # args: [--application-directories=.:src, --py36-plus] 29 | 30 | - repo: https://github.com/asottile/pyupgrade 31 | rev: v3.4.0 32 | hooks: 33 | - id: pyupgrade 34 | args: [--py36-plus] 35 | 36 | - repo: https://github.com/pre-commit/mirrors-mypy 37 | rev: v1.3.0 38 | hooks: 39 | - id: mypy 40 | files: ^src/ 41 | args: [] 42 | -------------------------------------------------------------------------------- /frontend/src/icons/CalenderIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | export type Icon = SVGAttributes; 3 | 4 | const CalenderIcon: FC = () => { 5 | return ( 6 | 7 | 14 | 15 | ); 16 | }; 17 | 18 | export default CalenderIcon; 19 | -------------------------------------------------------------------------------- /backend/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from peoples.models import Member 3 | from transaction.models import Savings 4 | 5 | 6 | class MemberSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = Member 9 | exclude = ('is_active', 'team', 'branch') 10 | 11 | 12 | class SavingPostingSerializer(serializers.Serializer): 13 | team = serializers.IntegerField() 14 | serial_number = serializers.IntegerField() 15 | amount = serializers.IntegerField() 16 | date = serializers.DateField() 17 | 18 | def create(self, validated_data): 19 | team = validated_data.get('team') 20 | serial_number = validated_data.get('serial_number') 21 | amount = validated_data.get('amount') 22 | date = validated_data.get('date') 23 | staff = validated_data.get('staff') 24 | 25 | print(date) 26 | 27 | member = Member.objects.get(team_id=team, serial_number=serial_number) 28 | # staff, branch 29 | savings = Savings.objects.create( 30 | amount=amount, 31 | date=date, 32 | member=member, 33 | staff=staff, 34 | branch=staff.branch 35 | ) 36 | return savings -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.7.2 2 | attrs==23.1.0 3 | black==23.3.0 4 | certifi==2023.5.7 5 | cfgv==3.3.1 6 | charset-normalizer==3.2.0 7 | click==8.1.4 8 | coreapi==2.3.3 9 | coreschema==0.0.4 10 | distlib==0.3.6 11 | dj-database-url==2.0.0 12 | Django==4.2.3 13 | django-cors-headers==4.2.0 14 | django-environ==0.10.0 15 | django-extensions==3.2.3 16 | django-filter==23.2 17 | djangorestframework==3.14.0 18 | djangorestframework-simplejwt==5.2.2 19 | drf-spectacular==0.26.3 20 | drf-yasg==1.21.6 21 | filelock==3.12.2 22 | gunicorn==20.1.0 23 | identify==2.5.24 24 | idna==3.4 25 | inflection==0.5.1 26 | itypes==1.2.0 27 | Jinja2==3.1.2 28 | jsonschema==4.18.0 29 | jsonschema-specifications==2023.6.1 30 | Markdown==3.4.3 31 | MarkupSafe==2.1.3 32 | mypy-extensions==1.0.0 33 | nodeenv==1.8.0 34 | packaging==23.1 35 | pathspec==0.11.1 36 | platformdirs==3.8.1 37 | pre-commit==3.3.3 38 | psycopg2-binary==2.9.6 39 | PyJWT==2.7.0 40 | pyrsistent==0.19.3 41 | pytz==2023.3 42 | PyYAML==6.0 43 | referencing==0.29.1 44 | requests==2.31.0 45 | rpds-py==0.8.10 46 | ruamel.yaml==0.17.32 47 | ruamel.yaml.clib==0.2.7 48 | sqlparse==0.4.4 49 | tomli==2.0.1 50 | typing_extensions==4.7.1 51 | uritemplate==4.1.1 52 | urllib3==2.0.3 53 | virtualenv==20.23.1 54 | whitenoise==6.5.0 55 | -------------------------------------------------------------------------------- /frontend/src/components/CustomDatePicker/CustomDatePicker.tsx: -------------------------------------------------------------------------------- 1 | import { FormLabel } from '@chakra-ui/react'; 2 | import React, { useEffect, useState } from 'react'; 3 | import DatePicker from 'react-datepicker'; 4 | import 'react-datepicker/dist/react-datepicker.css'; 5 | 6 | interface CustomDatePickerProps { 7 | label: string; 8 | setDate: (date: string) => void; 9 | } 10 | 11 | const CustomDatePicker: React.FC = ({ label, setDate }) => { 12 | const [startDate, setStartDate] = useState(new Date()); 13 | 14 | const formatDate = (date: Date | null): string => { 15 | if (!date) return ''; 16 | const year = date.getFullYear(); 17 | const month = ('0' + (date.getMonth() + 1)).slice(-2); 18 | const day = ('0' + date.getDate()).slice(-2); 19 | return `${year}-${month}-${day}`; 20 | }; 21 | 22 | useEffect(() => { 23 | setDate(formatDate(startDate)); 24 | }, [startDate]); 25 | 26 | return ( 27 | <> 28 | {label} 29 | setStartDate(date)} /> 30 | 31 | ); 32 | }; 33 | 34 | export default CustomDatePicker; 35 | -------------------------------------------------------------------------------- /backend/organization/migrations/0003_alter_user_groups_alter_user_is_superuser.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-29 08:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("auth", "0012_alter_user_first_name_max_length"), 9 | ("organization", "0002_remove_branch_thana"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="user", 15 | name="groups", 16 | field=models.ManyToManyField( 17 | blank=True, 18 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", 19 | related_name="user_set", 20 | related_query_name="user", 21 | to="auth.group", 22 | verbose_name="groups", 23 | ), 24 | ), 25 | migrations.AlterField( 26 | model_name="user", 27 | name="is_superuser", 28 | field=models.BooleanField( 29 | default=False, 30 | help_text="Designates that this user has all permissions without explicitly assigning them.", 31 | verbose_name="superuser status", 32 | ), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /frontend/src/icons/index.tsx: -------------------------------------------------------------------------------- 1 | import { SVGAttributes } from 'react'; 2 | 3 | export { default as BranchIcon } from './BranchIcon'; 4 | export { default as CalenderIcon } from './CalenderIcon'; 5 | export { default as CashHandIcon } from './CashHandIcon'; 6 | export { default as DepositIcon } from './DepositIcon'; 7 | export { default as ExpenseIcon } from './ExpenseIcon'; 8 | export { default as FilterIcon } from './FilterIcon'; 9 | export { default as IncomeIcon } from './IncomeIcon'; 10 | export { default as LoanIcon } from './LoanIcon'; 11 | export { default as LoanPlusIcon } from './LoanPlusIcon'; 12 | export { default as PersonPlusIcon } from './PersonPlusIcon'; 13 | export { default as PersonsIcon } from './PersonsIcon'; 14 | export { default as PlusIcon } from './PlusIcon'; 15 | export { default as PreviousIcon } from './PreviousIcon'; 16 | export { default as SearchIcon } from './SearchIcon'; 17 | export { default as VerticalDotIcon } from './VerticalDotIcon'; 18 | export { default as WithdrawIcon } from './WithdrawIcon'; 19 | 20 | export type IconProps = { 21 | height?: number | `${number}${'px' | 'rem' | 'em'}`; 22 | width?: number | `${number}${'px' | 'rem' | 'em'}`; 23 | className?: string; 24 | stroke?: string; 25 | fill?: string; 26 | strokeWidth?: number; 27 | } & SVGAttributes; 28 | -------------------------------------------------------------------------------- /backend/transaction/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Savings, Loan, GeneralTransaction 3 | 4 | from django.contrib.admin.sites import AlreadyRegistered 5 | from django.contrib import admin 6 | from django.apps import apps 7 | 8 | 9 | # write custom admin views 10 | @admin.register(Savings) 11 | class SavingsAdmin(admin.ModelAdmin): 12 | list_display = ("id", "date", "member", "team", "transaction_type", "amount", "balance") 13 | list_filter = ("member",) 14 | 15 | 16 | @admin.register(Loan) 17 | class LoanAdmin(admin.ModelAdmin): 18 | list_display = [ 19 | 'id', 'member', 'team','branch', 'is_paid', 'amount', 20 | 'total_installment', 'installment_paid', 'total_paid', 'total_due' 21 | ] 22 | list_display_links = ['id'] 23 | #list_editable = ['is_paid'] 24 | ordering = ['id'] 25 | 26 | 27 | @admin.register(GeneralTransaction) 28 | class GeneralTransaction(admin.ModelAdmin): 29 | list_display = ('id','amount', 'date', 'transaction_type', 'category', ) 30 | list_filter = ('date', 'category', 'transaction_type',) 31 | 32 | 33 | # register all the models from this app 34 | app_models = apps.get_app_config("transaction").get_models() 35 | for model in app_models: 36 | try: 37 | admin.site.register(model) 38 | except AlreadyRegistered: 39 | pass 40 | 41 | -------------------------------------------------------------------------------- /backend/templates/transaction/loan_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base/base.html' %} 2 | 3 | 4 | {% block content %} 5 | 6 |
7 |
8 | কর্জ শিট 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for journal in journals %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% endfor %} 37 |
SLনামঅভিভাবকের নামকর্জের পরিমানসপ্তাহ ১সপ্তাহ ২সপ্তাহ ৩সপ্তাহ ৪ব্যালেন্স
{{ journal.sl }}{{ journal.member_name }}{{ journal.guardian_name }}{{ journal.loan_amount }}{{ journal.week1 }}{{ journal.week2 }}{{ journal.week3 }}{{ journal.week4 }}{{ journal.loan_balance }}
38 |
39 |
40 | 41 | {% endblock %} -------------------------------------------------------------------------------- /frontend/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/icons/PersonsIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | export type Icon = SVGAttributes; 3 | 4 | const PersonsIcon: FC = () => { 5 | return ( 6 | 7 | 14 | 15 | ); 16 | }; 17 | 18 | export default PersonsIcon; 19 | -------------------------------------------------------------------------------- /frontend/src/utils/sweatalert.ts: -------------------------------------------------------------------------------- 1 | import Swal, { SweetAlertIcon } from 'sweetalert2'; 2 | 3 | interface AlertOptions { 4 | title?: string; 5 | text?: string; 6 | icon?: SweetAlertIcon; 7 | } 8 | 9 | export const showAlert = ({ 10 | title = 'Congratulations!', 11 | text = 'Action successful!', 12 | icon = 'success', 13 | }: AlertOptions): void => { 14 | Swal.fire({ 15 | icon: icon, 16 | iconColor: '#039855', 17 | title: title, 18 | text: text, 19 | timer: 3000, 20 | showConfirmButton: false, 21 | }); 22 | }; 23 | 24 | interface ToastOptions { 25 | icon?: SweetAlertIcon; 26 | title?: string; 27 | } 28 | 29 | export const toastify = ({ icon = 'success', title = 'Action Successful' }: ToastOptions): void => { 30 | const Toast = Swal.mixin({ 31 | toast: true, 32 | position: 'bottom-end', 33 | showConfirmButton: false, 34 | timer: 3000, 35 | timerProgressBar: true, 36 | didOpen: (toast) => { 37 | toast.addEventListener('mouseenter', Swal.stopTimer); 38 | toast.addEventListener('mouseleave', Swal.resumeTimer); 39 | }, 40 | customClass: { 41 | container: 'swal-toast-container', 42 | }, 43 | }); 44 | Toast.fire({ 45 | icon: icon, 46 | title: title, 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /backend/organization/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin.sites import AlreadyRegistered 2 | from django.contrib import admin 3 | from django.apps import apps 4 | 5 | from .models import Team, User, Branch 6 | 7 | 8 | class CustomUserAdmin(admin.ModelAdmin): 9 | list_display = ["id", "username", "is_staff", "is_superuser", "is_active"] 10 | 11 | def save_model(self, request, obj, form, change): 12 | # Hash the password only if it has been modified 13 | if "password" in form.changed_data: 14 | obj.set_password(obj.password) 15 | super().save_model(request, obj, form, change) 16 | 17 | 18 | @admin.register(User) 19 | class CustomUser(CustomUserAdmin): 20 | pass 21 | 22 | 23 | @admin.register(Team) 24 | class TeamAdmin(admin.ModelAdmin): 25 | list_display = ["id", "name", "branch", "owner"] 26 | list_display_links = ["name"] 27 | ordering = ["id"] 28 | 29 | 30 | @admin.register(Branch) 31 | class BranchAdmin(admin.ModelAdmin): 32 | list_display = ["id", "name", "code", "organization", "address", "bank_account"] 33 | list_display_links = ["name"] 34 | ordering = ["id"] 35 | 36 | 37 | # register all the models from this app 38 | app_models = apps.get_app_config("organization").get_models() 39 | for model in app_models: 40 | try: 41 | admin.site.register(model) 42 | except AlreadyRegistered: 43 | pass 44 | -------------------------------------------------------------------------------- /frontend/src/modules/branch/components/EditBranchModal.tsx: -------------------------------------------------------------------------------- 1 | import CustomTextInput from '@/components/CustomInput'; 2 | import { 3 | Button, 4 | Modal, 5 | ModalBody, 6 | ModalCloseButton, 7 | ModalContent, 8 | ModalFooter, 9 | ModalHeader, 10 | ModalOverlay, 11 | } from '@chakra-ui/react'; 12 | 13 | interface IEditBranchModal { 14 | isOpen: boolean; 15 | onClose: () => void; 16 | } 17 | 18 | const EditBranchModal: React.FC = ({ isOpen, onClose }) => { 19 | return ( 20 | 21 | 22 | 23 | 24 | Edit branch 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | export default EditBranchModal; 42 | -------------------------------------------------------------------------------- /frontend/src/modules/team/components/EditGroupModal.tsx: -------------------------------------------------------------------------------- 1 | import CustomTextInput from '@/components/CustomInput'; 2 | import { 3 | Button, 4 | Modal, 5 | ModalBody, 6 | ModalCloseButton, 7 | ModalContent, 8 | ModalFooter, 9 | ModalHeader, 10 | ModalOverlay, 11 | } from '@chakra-ui/react'; 12 | 13 | interface ICreateGroupModal { 14 | isOpen: boolean; 15 | onClose: () => void; 16 | } 17 | 18 | const EditGroupModal: React.FC = ({ isOpen, onClose }) => { 19 | return ( 20 | 21 | 22 | 23 | 24 | Edit the group Info 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | export default EditGroupModal; 42 | -------------------------------------------------------------------------------- /frontend/src/modules/branch/components/CreateBranchModal.tsx: -------------------------------------------------------------------------------- 1 | import CustomTextInput from '@/components/CustomInput'; 2 | import { 3 | Button, 4 | Modal, 5 | ModalBody, 6 | ModalCloseButton, 7 | ModalContent, 8 | ModalFooter, 9 | ModalHeader, 10 | ModalOverlay, 11 | } from '@chakra-ui/react'; 12 | 13 | interface ICreateBranchModal { 14 | isOpen: boolean; 15 | onClose: () => void; 16 | } 17 | 18 | const CreateBranchModal: React.FC = ({ isOpen, onClose }) => { 19 | return ( 20 | 21 | 22 | 23 | 24 | Create new branch 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | export default CreateBranchModal; 42 | -------------------------------------------------------------------------------- /frontend/src/icons/IncomeIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | 3 | export type Icon = SVGAttributes; 4 | 5 | const IncomeIcon: FC = () => { 6 | return ( 7 | 8 | 15 | 16 | ); 17 | }; 18 | 19 | export default IncomeIcon; 20 | -------------------------------------------------------------------------------- /backend/templates/transaction/deposit_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base/base.html' %} 2 | 3 | 4 | {% block content %} 5 | 6 |
7 |
8 | Deposit List 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for journal in journals %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | {% endfor %} 39 |
SLনামঅভিভাবকের নামসপ্তাহ ১সপ্তাহ ২সপ্তাহ ৩সপ্তাহ ৪ব্যালেন্স
{{ journal.sl }}{{ journal.member_name }}{{ journal.guardian_name }}{{ journal.week1 }}{{ journal.week2 }}{{ journal.week3 }}{{ journal.week4 }}{{ journal.balance }} 34 | কর্জ প্রদান 35 |
40 |
41 |
42 | 43 | {% endblock %} -------------------------------------------------------------------------------- /frontend/src/schema/AuthSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const loginSchema = z.object({ 4 | username: z.string().trim().min(1, { message: 'Please insert your username.' }), 5 | // .username('This is not a valid username.'), 6 | password: z.string().min(1, { message: 'Please insert your password.' }), 7 | }); 8 | 9 | export const registrationSchema = z 10 | .object({ 11 | organizationName: z.string().trim().min(1, { message: 'Please insert your organization name.' }).max(50), 12 | address: z.string().trim().min(1, { message: 'Please insert your address.' }).max(50), 13 | email: z 14 | .string() 15 | .trim() 16 | .min(1, { message: 'Please insert your address.' }) 17 | .email({ message: 'Invalid username' }), 18 | phoneNumber: z 19 | .string() 20 | .regex(new RegExp(/^(?:\+88|88)?(01[3-9]\d{8})$/), { message: 'Not a valid Bangladeshi number' }), 21 | 22 | password: z.string().min(5, { message: 'Please insert your password.' }), 23 | confirmPassword: z.string().min(5, { message: 'Please insert your confirm password.' }), 24 | }) 25 | .refine((data) => data.password === data.confirmPassword, { 26 | message: 'Password do not match', 27 | path: ['confirmPassword'], 28 | }); 29 | 30 | export type LoginType = z.infer; 31 | export type RegistrationType = z.infer; 32 | -------------------------------------------------------------------------------- /backend/static/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@coreui/static", 3 | "version": "1.0.6", 4 | "description": "Open Source Bootstrap Admin Template", 5 | "main": "index.html", 6 | "dependencies": { 7 | "bootstrap": "4.0.0-beta.2", 8 | "chart.js": "2.7.1", 9 | "font-awesome": "4.7.0", 10 | "jquery": "3.2.1", 11 | "pace-progress": "1.0.2", 12 | "popper.js": "1.12.6", 13 | "simple-line-icons": "2.4.1" 14 | }, 15 | "devDependencies": { 16 | "browser-sync": "2.18.13", 17 | "del": "3.0.0", 18 | "gulp": "3.9.1", 19 | "gulp-autoprefixer": "^4.0.0", 20 | "gulp-bower-src": "0.1.0", 21 | "gulp-concat": "2.6.1", 22 | "gulp-contains": "1.1.0", 23 | "gulp-cssmin": "0.2.0", 24 | "gulp-filter": "5.0.1", 25 | "gulp-rename": "1.2.2", 26 | "gulp-replace": "0.6.1", 27 | "gulp-sass": "3.1.0", 28 | "gulp-search": "1.0.0", 29 | "gulp-uglify": "3.0.0", 30 | "require-dir": "0.3.2", 31 | "run-sequence": "2.2.0" 32 | }, 33 | "scripts": { 34 | "gulp": "gulp", 35 | "build": "gulp build:dist", 36 | "serve": "gulp serve", 37 | "serve-dist": "gulp serve:dist" 38 | }, 39 | "browserslist": [ 40 | "Chrome >= 45", 41 | "Firefox ESR", 42 | "Edge >= 12", 43 | "Explorer >= 10", 44 | "iOS >= 9", 45 | "Safari >= 9", 46 | "Android >= 4.4", 47 | "Opera >= 30" 48 | ], 49 | "author": "Łukasz Holeczek", 50 | "homepage": "http://coreui.io", 51 | "copyright": "Copyright 2017 creativeLabs Łukasz Holeczek", 52 | "license": "MIT" 53 | } 54 | -------------------------------------------------------------------------------- /frontend/middleware.ts: -------------------------------------------------------------------------------- 1 | // middleware.ts 2 | import { getToken } from 'next-auth/jwt'; 3 | import { NextRequest, NextResponse } from 'next/server'; 4 | // paths that require authentication or authorization 5 | const requireAuth: string[] = ['/dashboard', '/branch', '/team', '/withdraw', '/member']; 6 | const notRequired: string[] = ['/login', '/registration']; 7 | 8 | export async function middleware(request: NextRequest) { 9 | const res = NextResponse.next(); 10 | const pathname = request.nextUrl.pathname; 11 | 12 | if (notRequired.some((path) => pathname.startsWith(path))) { 13 | return res; 14 | } 15 | if (requireAuth.some((path) => pathname.startsWith(path))) { 16 | const token = await getToken({ 17 | req: request, 18 | secret: process.env.NEXTAUTH_SECRET, 19 | }); 20 | //check not logged in 21 | if (!token) { 22 | const url = new URL(`/login`, request.url); 23 | url.searchParams.set('callbackUrl', encodeURI(request.url)); 24 | return NextResponse.redirect(url); 25 | } 26 | // if (token) { 27 | // const branch_id = token?.details?.user?.branch; 28 | // return NextResponse.redirect(new URL(`/branch/${branch_id}`, request.url)); 29 | // } 30 | //check if not authorized 31 | // if (token.role !== 'admin') { 32 | // const url = new URL(`/403`, request.url); 33 | // return NextResponse.rewrite(url); 34 | // } 35 | } 36 | return res; 37 | } 38 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_dropdown.scss: -------------------------------------------------------------------------------- 1 | // Links, buttons, and more within the dropdown menu 2 | .dropdown-item { 3 | position: relative; 4 | padding: 10px 20px; 5 | border-bottom: 1px solid $dropdown-border-color; 6 | 7 | &:last-child { 8 | border-bottom: 0; 9 | } 10 | 11 | i { 12 | display: inline-block; 13 | width: 20px; 14 | margin-right: 10px; 15 | margin-left: -10px; 16 | color: $dropdown-border-color; 17 | text-align: center; 18 | } 19 | 20 | .badge { 21 | position: absolute; 22 | right: 10px; 23 | margin-top: 2px; 24 | } 25 | } 26 | 27 | // Dropdown section headers 28 | .dropdown-header { 29 | padding: 8px 20px; 30 | background: $dropdown-divider-bg; 31 | border-bottom: 1px solid $dropdown-border-color; 32 | 33 | .btn { 34 | margin-top: -7px; 35 | color: $dropdown-header-color; 36 | 37 | &:hover { 38 | color: $body-color; 39 | } 40 | 41 | &.pull-right { 42 | margin-right: -20px; 43 | } 44 | } 45 | } 46 | 47 | .dropdown-menu-lg { 48 | width: 250px; 49 | } 50 | .app-header { 51 | .navbar-nav { 52 | .dropdown-menu { 53 | position: absolute; 54 | } 55 | // Menu positioning 56 | // 57 | // Add extra class to `.dropdown-menu` to flip the alignment of the dropdown 58 | // menu with the parent. 59 | .dropdown-menu-right { 60 | right: 0; 61 | left: auto; // Reset the default from `.dropdown-menu` 62 | } 63 | 64 | .dropdown-menu-left { 65 | right: auto; 66 | left: 0; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /backend/organization/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.shortcuts import render, get_object_or_404, redirect 3 | from .models import Team 4 | from .forms import TeamForm 5 | 6 | # Create a new team 7 | @login_required 8 | def team_create(request): 9 | if request.method == 'POST': 10 | form = TeamForm(request.POST) 11 | if form.is_valid(): 12 | form.save() 13 | return redirect('team_list') 14 | else: 15 | form = TeamForm() 16 | return render(request, 'team_form.html', {'form': form}) 17 | 18 | 19 | # Read/Display list of teams 20 | @login_required 21 | def team_list(request): 22 | teams = Team.objects.filter(branch=request.user.branch) 23 | return render(request, 'org/team_list.html', {'teams': teams}) 24 | 25 | 26 | # Update an existing team 27 | @login_required 28 | def team_update(request, pk): 29 | team = get_object_or_404(Team, pk=pk) 30 | if request.method == 'POST': 31 | form = TeamForm(request.POST, instance=team) 32 | if form.is_valid(): 33 | form.save() 34 | return redirect('team_list') 35 | else: 36 | form = TeamForm(instance=team) 37 | return render(request, 'team_form.html', {'form': form}) 38 | 39 | # Delete a team 40 | @login_required 41 | def team_delete(request, pk): 42 | team = get_object_or_404(Team, pk=pk) 43 | if request.method == 'POST': 44 | team.delete() 45 | return redirect('team_list') 46 | return render(request, 'team_confirm_delete.html', {'team': team}) -------------------------------------------------------------------------------- /frontend/src/components/CustomInput/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormControl, FormErrorMessage, FormHelperText, FormLabel, Input, InputProps } from '@chakra-ui/react'; 2 | import { forwardRef, useId } from 'react'; 3 | 4 | interface ICustomTextInput extends InputProps { 5 | className?: string; 6 | label?: string; 7 | error?: boolean | string; 8 | helperText?: string; 9 | } 10 | 11 | const CustomTextInput: React.FC = forwardRef( 12 | ({ className, label, error, helperText, name, ...rest }, ref) => { 13 | const uid = useId(); 14 | return ( 15 | 16 | {label ? ( 17 | 18 | {label} 19 | 20 | ) : null} 21 | 30 | {helperText} 31 | {error ? {error} : null} 32 | 33 | ); 34 | } 35 | ); 36 | 37 | CustomTextInput.displayName = 'CustomTextInput'; 38 | 39 | export default CustomTextInput; 40 | -------------------------------------------------------------------------------- /backend/api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.views import APIView 2 | from rest_framework.response import Response 3 | from rest_framework import status 4 | from peoples.models import Member 5 | from transaction.models import Savings 6 | from api.serializers import MemberSerializer, SavingPostingSerializer 7 | 8 | 9 | class GetMemberView(APIView): 10 | def get(self, request, team_id, serial_number): 11 | user_branch = request.user.branch 12 | try: 13 | member = Member.objects.get(branch=user_branch, team_id=team_id, serial_number=serial_number, is_active=True) 14 | total_savings = Savings.objects.member_total_savings(member) 15 | serializer = MemberSerializer(member) 16 | data = serializer.data 17 | data['total_savings'] = total_savings 18 | return Response(data) 19 | except Exception as e: 20 | data = { 21 | "msg": str(e) 22 | } 23 | return Response(data, status=status.HTTP_400_BAD_REQUEST) 24 | 25 | 26 | class SavingsPostingAPI(APIView): 27 | def post(self, request): 28 | print(request.data) 29 | serializer = SavingPostingSerializer(data=request.data) 30 | if serializer.is_valid(): 31 | try: 32 | serializer.save(staff=request.user.staff) 33 | return Response({'success': True}, status=status.HTTP_200_OK) 34 | except Exception as e: 35 | return Response({'success': False, 'message': str(e)}, status=status.HTTP_400_BAD_REQUEST) 36 | return Response({'success': False, 'message': serializer.errors}, status=status.HTTP_400_BAD_REQUEST) -------------------------------------------------------------------------------- /frontend/src/icons/VerticalDotIcon.tsx: -------------------------------------------------------------------------------- 1 | import { IconProps } from '.'; 2 | 3 | const VerticalDotIcon: React.FC = ({ 4 | className = '', 5 | height = '24', 6 | width = '24', 7 | stroke = 'black', 8 | strokeWidth = '2', 9 | ...rest 10 | }) => { 11 | return ( 12 | 21 | 28 | 35 | 42 | 43 | ); 44 | }; 45 | 46 | export default VerticalDotIcon; 47 | -------------------------------------------------------------------------------- /backend/peoples/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from peoples.models import Member, Staff 4 | 5 | 6 | class MemberDetailSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = Member 9 | fields = ('id', 'name', 'mobile_number', 'guardian_name', 10 | 'serial_number', 'team', 'branch', ) 11 | 12 | def to_representation(self, instance): 13 | data = super().to_representation(instance) 14 | data.update({ 15 | "team": instance.team.name if instance.team else "", 16 | "branch": instance.branch.name if instance.branch else "", 17 | "joined_date": instance.created_at 18 | }) 19 | return data 20 | 21 | 22 | class MemberCreateSerializer(serializers.ModelSerializer): 23 | class Meta: 24 | model = Member 25 | fields = [ 26 | "name", 27 | "mobile_number", 28 | "nid_number", 29 | "guardian_name", 30 | "gender", 31 | "serial_number", 32 | "team", 33 | ] 34 | 35 | def validate_serial_number(self, value): 36 | if value > 25: 37 | raise serializers.ValidationError( 38 | "Serial number must not be greater than 25." 39 | ) 40 | return value 41 | 42 | 43 | class MemberSavingsLoanInfoSerializer(serializers.Serializer): 44 | total_savings = serializers.IntegerField() 45 | last_loan = serializers.IntegerField() 46 | loan_date = serializers.DateField() 47 | loan_paid = serializers.IntegerField() 48 | installment_paid = serializers.IntegerField() 49 | total_loan_count = serializers.IntegerField() 50 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_aside.scss: -------------------------------------------------------------------------------- 1 | .aside-menu { 2 | z-index: $zindex-sticky - 1; 3 | width: $aside-menu-width; 4 | color: $aside-menu-color; 5 | background: $aside-menu-bg; 6 | @include borders($aside-menu-borders); 7 | 8 | .nav-tabs { 9 | border-color: $border-color; 10 | .nav-link { 11 | padding: $aside-menu-nav-padding-y $aside-menu-nav-padding-x; 12 | color: $body-color; 13 | border-top: 0; 14 | &.active { 15 | color: theme-color("primary"); 16 | border-right-color: $border-color; 17 | border-left-color: $border-color; 18 | } 19 | } 20 | .nav-item:first-child { 21 | .nav-link { 22 | border-left: 0; 23 | } 24 | } 25 | } 26 | 27 | .tab-content { 28 | position: relative; 29 | overflow-x: hidden; 30 | overflow-y: auto; 31 | border: 0; 32 | border-top: 1px solid $border-color; 33 | -ms-overflow-style: -ms-autohiding-scrollbar; 34 | 35 | &::-webkit-scrollbar { 36 | width: 10px; 37 | margin-left: -10px; 38 | -webkit-appearance: none; 39 | } 40 | 41 | // &::-webkit-scrollbar-button { } 42 | 43 | &::-webkit-scrollbar-track { 44 | background-color: lighten($aside-menu-bg, 5%); 45 | border-right: 1px solid darken($aside-menu-bg, 5%); 46 | border-left: 1px solid darken($aside-menu-bg, 5%); 47 | } 48 | 49 | // &::-webkit-scrollbar-track-piece { } 50 | 51 | &::-webkit-scrollbar-thumb { 52 | height: 50px; 53 | background-color: darken($aside-menu-bg, 10%); 54 | background-clip: content-box; 55 | border-color: transparent; 56 | border-style: solid; 57 | border-width: 1px 2px; 58 | } 59 | 60 | .tab-pane { 61 | padding: 0; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /frontend/src/components/CustomSelect/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormControl, FormErrorMessage, FormHelperText, FormLabel, Select } from '@chakra-ui/react'; 2 | import { forwardRef, useId } from 'react'; 3 | type OptionType = { label: string; value: string | number }; 4 | 5 | interface ICustomSelectInput { 6 | className?: string; 7 | label?: string; 8 | error?: boolean | string; 9 | helperText?: string; 10 | name?: string; 11 | options: Array; 12 | } 13 | 14 | const CustomSelectInput: React.FC = forwardRef( 15 | ({ name, helperText, options, error, label, className, ...rest }, ref) => { 16 | const uid = useId(); 17 | return ( 18 | 19 | {label ? ( 20 | 21 | {label} 22 | 23 | ) : null} 24 | 37 | {helperText} 38 | {error ? {error} : null} 39 | 40 | ); 41 | } 42 | ); 43 | 44 | export default CustomSelectInput; 45 | -------------------------------------------------------------------------------- /frontend/src/icons/BranchIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | export type Icon = SVGAttributes; 3 | 4 | const BranchIcon: FC = () => { 5 | return ( 6 | 7 | 14 | 15 | ); 16 | }; 17 | 18 | export default BranchIcon; 19 | -------------------------------------------------------------------------------- /backend/transaction/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from organization.models import Team 3 | from peoples.models import Member 4 | from .models import Loan 5 | 6 | 7 | class MemberChoiceForm(forms.Form): 8 | team = forms.ModelChoiceField(queryset=Team.objects.all()) 9 | serial_number = forms.IntegerField() 10 | 11 | def __init__(self, *args, **kwargs): 12 | user = kwargs.pop('user') # Extract the user from the kwargs 13 | super(MemberChoiceForm, self).__init__(*args, **kwargs) 14 | 15 | # Filter the queryset of the project field based on the user 16 | self.fields['team'].queryset = Team.objects.filter(owner=user) 17 | 18 | 19 | class DepositForm(forms.Form): 20 | date = forms.DateField(widget=forms.DateInput(attrs={'class': 'form-control'})) 21 | amount = forms.IntegerField(widget=forms.NumberInput(attrs={'class': 'form-control'})) 22 | 23 | 24 | class InstallmentForm(forms.Form): 25 | date = forms.DateField(widget=forms.DateInput(attrs={'class': 'form-control'})) 26 | amount = forms.IntegerField(widget=forms.NumberInput(attrs={'class': 'form-control'})) 27 | 28 | 29 | class LoanDisbursementForm(forms.ModelForm): 30 | class Meta: 31 | model = Loan 32 | fields = ['date', 'amount', 'total_installment'] 33 | 34 | def clean(self): 35 | cleaned_data = super().clean() 36 | amount = cleaned_data.get('amount') 37 | total_installment = cleaned_data.get('total_installment') 38 | total_installment = cleaned_data.get('total_installment') 39 | 40 | if amount <= 0: 41 | self.add_error('amount', "Loan amount must be positive.") 42 | 43 | if total_installment <= 0: 44 | self.add_error('total_installment', "Total installments must be a positive number.") 45 | 46 | return cleaned_data 47 | -------------------------------------------------------------------------------- /backend/transaction/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import GeneralTransaction, Savings, Loan, Installment, TransactionCategory 3 | from journal.models import GeneralJournal, Ledger 4 | 5 | 6 | class DepositSerializer(serializers.ModelSerializer): 7 | amount = serializers.IntegerField() 8 | 9 | class Meta: 10 | model = GeneralJournal 11 | fields = ("member", "amount", "date") 12 | 13 | 14 | class LoanDisbursementSerializer(serializers.ModelSerializer): 15 | class Meta: 16 | model = Loan 17 | fields = ("amount", "date", "member", "total_installment") 18 | 19 | 20 | class LoanInstallmentSerializer(serializers.ModelSerializer): 21 | class Meta: 22 | model = Installment 23 | fields = ("amount", "date", "loan") 24 | 25 | def validate(self, attrs): 26 | loan = attrs.get("loan") 27 | if loan.is_paid: 28 | raise serializers.ValidationError("Loan is already paid") 29 | return attrs 30 | 31 | 32 | class GeneralTransactionSerializer(serializers.ModelSerializer): 33 | class Meta: 34 | model = GeneralTransaction 35 | fields = ( 36 | "id", 37 | "amount", 38 | "date", 39 | "category", 40 | "summary", 41 | ) 42 | 43 | def to_representation(self, instance): 44 | data = super().to_representation(instance) 45 | data.update( 46 | { 47 | "category": instance.category.name, 48 | } 49 | ) 50 | return data 51 | 52 | 53 | class TransactionCategorySerializer(serializers.ModelSerializer): 54 | class Meta: 55 | model = TransactionCategory 56 | fields = ( 57 | "id", 58 | "name", 59 | "category_type" 60 | ) 61 | -------------------------------------------------------------------------------- /frontend/src/icons/CashHandIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGAttributes } from 'react'; 2 | 3 | export type Icon = SVGAttributes; 4 | 5 | const CashHandIcon: FC = () => { 6 | return ( 7 | 8 | 15 | 16 | ); 17 | }; 18 | 19 | export default CashHandIcon; 20 | -------------------------------------------------------------------------------- /backend/korjo_soft/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib import admin 3 | from django.urls import path, include 4 | from django.conf.urls.static import static 5 | 6 | from rest_framework_simplejwt.views import TokenRefreshView 7 | from drf_spectacular.views import ( 8 | SpectacularAPIView, 9 | SpectacularRedocView, 10 | SpectacularSwaggerView, 11 | ) 12 | 13 | from organization.api import ChangePasswordAPIView, LoginView, RegisterView, LogoutView 14 | 15 | 16 | urlpatterns = [ 17 | path("admin/", admin.site.urls), 18 | # api docs 19 | path("schema/", SpectacularAPIView.as_view(), name="schema"), 20 | # Optional UI: 21 | path("", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"), 22 | path("redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"), 23 | # authentications 24 | path("api/v1/auth/login/", LoginView.as_view(), name="login"), 25 | path("api/v1/auth/logout/", LogoutView.as_view(), name="logout"), 26 | path("api/v1/auth/registration/", RegisterView.as_view(), name="register"), 27 | path("api/v1/auth/password/change/", ChangePasswordAPIView.as_view(), name="change_password"), 28 | path("api/v1/auth/refresh/", TokenRefreshView.as_view(), name="refresh"), 29 | # organization 30 | path("api/v1/organization/", include("organization.routes")), 31 | # peoples 32 | path("api/v1/peoples/", include("peoples.urls")), 33 | # transaction 34 | path("api/v1/transaction/", include("transaction.routes")), 35 | 36 | path("dashboard/transactions/", include("transaction.urls")), 37 | path("dashboard/org/", include("organization.urls")), 38 | path("accounts/", include("accounts.urls")) 39 | ] 40 | 41 | if settings.DEBUG: 42 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 43 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 44 | -------------------------------------------------------------------------------- /backend/static/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var gulp = require('gulp'); 4 | var browserSync = require('browser-sync').create(); 5 | var sass = require('gulp-sass'); 6 | var autoprefixer = require('gulp-autoprefixer'); 7 | var cssmin = require('gulp-cssmin') 8 | var rename = require('gulp-rename'); 9 | var runSequence = require('run-sequence'); 10 | 11 | gulp.paths = { 12 | dist: 'dist/', 13 | src: 'src/', 14 | vendors: 'dist/vendors/' 15 | }; 16 | 17 | var paths = gulp.paths; 18 | 19 | require('require-dir')('./gulp-tasks'); 20 | 21 | // Static Server + watching scss/html files 22 | gulp.task('serve', ['sass'], function() { 23 | 24 | browserSync.init({ 25 | server: ['./', './src'] 26 | }); 27 | 28 | gulp.watch(paths.src + 'scss/**/*.scss', ['sass']); 29 | gulp.watch(paths.src + '**/*.html').on('change', browserSync.reload); 30 | gulp.watch(paths.src + 'js/**/*.js').on('change', browserSync.reload); 31 | 32 | }); 33 | 34 | // Static Server without watching scss files 35 | gulp.task('serve:lite', function() { 36 | 37 | browserSync.init({ 38 | server: ['./', './src'] 39 | }); 40 | 41 | gulp.watch(paths.src + '**/*.css').on('change', browserSync.reload); 42 | gulp.watch(paths.src + '**/*.html').on('change', browserSync.reload); 43 | gulp.watch(paths.src + 'js/**/*.js').on('change', browserSync.reload); 44 | 45 | }); 46 | 47 | gulp.task('serve:dist', function() { 48 | browserSync.init({ 49 | server: ['./dist'] 50 | }); 51 | }); 52 | 53 | gulp.task('sass', ['compile-vendors'], function() { 54 | return gulp.src(paths.src + '/scss/style.scss') 55 | .pipe(sass()) 56 | .pipe(autoprefixer()) 57 | .pipe(gulp.dest(paths.src + 'css')) 58 | .pipe(cssmin()) 59 | .pipe(rename({suffix: '.min'})) 60 | .pipe(gulp.dest(paths.src + 'css')) 61 | .pipe(browserSync.stream()); 62 | }); 63 | 64 | gulp.task('sass:watch', function() { 65 | gulp.watch(paths.src + 'scss/**/*.scss', ['sass']); 66 | }); 67 | 68 | gulp.task('default', ['serve']); 69 | -------------------------------------------------------------------------------- /frontend/styles/globals.css: -------------------------------------------------------------------------------- 1 | 2 | /* styles/global.css */ 3 | 4 | @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Bengali&display=swap'); 5 | 6 | @font-face { 7 | font-family: 'Nikosh'; 8 | src: url('/fonts/Nikosh.ttf') format('ttf'); 9 | font-weight: normal; 10 | font-style: normal; 11 | font-display: swap; 12 | } 13 | @font-face { 14 | font-family: 'Boisakh'; 15 | src: url('/fonts/LiAlinurBoisakhUnicode-Regular.ttf') format('ttf'); 16 | font-weight: normal; 17 | font-style: normal; 18 | font-display: swap; 19 | } 20 | 21 | 22 | * { 23 | padding: 0; 24 | margin: 0; 25 | box-sizing: border-box; 26 | font-family: 'Inter','Nikosh', sans-serif; 27 | } 28 | 29 | html, 30 | body { 31 | font-family: 'Inter', sans-serif; 32 | } 33 | 34 | a { 35 | text-decoration: none; 36 | } 37 | 38 | /* ====================== this styles are for the scroll bar =============== */ 39 | 40 | body { 41 | --sb-track-color: #ffffff; 42 | --sb-thumb-color: #ccccccb0; 43 | --sb-size: 10px; 44 | 45 | scrollbar-color: var(--sb-thumb-color) 46 | var(--sb-track-color); 47 | } 48 | 49 | body::-webkit-scrollbar { 50 | width: var(--sb-size) 51 | } 52 | 53 | body::-webkit-scrollbar-track { 54 | background: var(--sb-track-color); 55 | border-radius: 8px; 56 | } 57 | 58 | body::-webkit-scrollbar-thumb { 59 | background: var(--sb-thumb-color); 60 | border-radius: 8px; 61 | border: 2px solid #aeb7b38b; 62 | } 63 | 64 | /*======================= scrollbar styling done ================= */ 65 | 66 | 67 | .react-datepicker-wrapper { 68 | width: 100%; 69 | } 70 | 71 | .react-datepicker__input-container { 72 | width: 100%; 73 | } 74 | 75 | .react-datepicker__input-container input { 76 | width: 100%; 77 | border: 2px solid green; 78 | border-radius: 5px; 79 | padding: 5px 10px; 80 | } 81 | 82 | .react-datepicker { 83 | font-size: 32px; /* Increase this value to make the calendar larger */ 84 | } 85 | -------------------------------------------------------------------------------- /backend/report/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.2 on 2023-07-24 22:16 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | initial = True 10 | 11 | dependencies = [ 12 | ("organization", "0001_initial"), 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name="CIHCalculation", 19 | fields=[ 20 | ( 21 | "id", 22 | models.BigAutoField( 23 | auto_created=True, 24 | primary_key=True, 25 | serialize=False, 26 | verbose_name="ID", 27 | ), 28 | ), 29 | ("created_at", models.DateTimeField(auto_now_add=True)), 30 | ("date", models.DateField()), 31 | ("cache_in_hand", models.IntegerField(default=0)), 32 | ( 33 | "branch", 34 | models.ForeignKey( 35 | on_delete=django.db.models.deletion.PROTECT, 36 | to="organization.branch", 37 | ), 38 | ), 39 | ( 40 | "created_by", 41 | models.ForeignKey( 42 | blank=True, 43 | null=True, 44 | on_delete=django.db.models.deletion.SET_NULL, 45 | to=settings.AUTH_USER_MODEL, 46 | ), 47 | ), 48 | ( 49 | "organization", 50 | models.ForeignKey( 51 | on_delete=django.db.models.deletion.PROTECT, 52 | to="organization.organization", 53 | ), 54 | ), 55 | ], 56 | options={ 57 | "abstract": False, 58 | }, 59 | ), 60 | ] 61 | -------------------------------------------------------------------------------- /backend/static/vendors.json: -------------------------------------------------------------------------------- 1 | { 2 | "css": [ 3 | "node_modules/codemirror/lib/codemirror.css", 4 | "node_modules/datatables.net-bs4/css/dataTables.bootstrap4.css", 5 | "node_modules/font-awesome/css/font-awesome.min.css", 6 | "node_modules/font-awesome/css/font-awesome.css.map", 7 | "node_modules/ladda/dist/ladda-themeless.min.css", 8 | "node_modules/quill/dist/quill.snow.css", 9 | "node_modules/simple-line-icons/css/simple-line-icons.css", 10 | "node_modules/spinkit/css/spinkit.css" 11 | ], 12 | "fonts": [ 13 | "node_modules/font-awesome/fonts/**", 14 | "node_modules/simple-line-icons/fonts/**" 15 | ], 16 | "js": [ 17 | "node_modules/bootstrap/dist/js/bootstrap.min.js", 18 | "node_modules/bootstrap-daterangepicker/daterangepicker.js", 19 | "node_modules/chart.js/dist/Chart.min.js", 20 | "node_modules/codemirror/lib/codemirror.js", 21 | "node_modules/codemirror/mode/markdown/markdown.js", 22 | "node_modules/codemirror/mode/xml/xml.js", 23 | "node_modules/datatables.net/js/jquery.dataTables.js", 24 | "node_modules/datatables.net-bs4/js/dataTables.bootstrap4.js", 25 | "node_modules/fullcalendar/dist/fullcalendar.min.js", 26 | "node_modules/fullcalendar/dist/gcal.min.js", 27 | "node_modules/gaugeJS/dist/gauge.min.js", 28 | "node_modules/ion-rangeslider/js/ion.rangeSlider.min.js", 29 | "node_modules/jquery/dist/jquery.min.js", 30 | "node_modules/jquery/dist/jquery.min.map", 31 | "node_modules/jquery-ui-dist/jquery-ui.min.js", 32 | "node_modules/jquery-validation/dist/jquery.validate.min.js", 33 | "node_modules/jquery.maskedinput/src/jquery.maskedinput.js", 34 | "node_modules/ladda/dist/ladda.min.js", 35 | "node_modules/ladda/dist/spin.min.js", 36 | "node_modules/moment/min/moment.min.js", 37 | "node_modules/quill/dist/quill.min.js", 38 | "node_modules/quill/dist/quill.min.js.map", 39 | "node_modules/pace-progress/pace.min.js", 40 | "node_modules/popper.js/dist/umd/popper.min.js", 41 | "node_modules/popper.js/dist/umd/popper.min.js.map", 42 | "node_modules/select2/dist/js/select2.min.js", 43 | "node_modules/toastr/toastr.js" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/utils/zodSafeQuery.ts: -------------------------------------------------------------------------------- 1 | import { AxiosRequestConfig, Method } from 'axios'; 2 | 3 | import http from '@/utils/http'; 4 | import invariant from 'tiny-invariant'; 5 | import { ZodSchema, z } from 'zod'; 6 | 7 | function zodSafeQuery< 8 | ParamsSchema extends ZodSchema = any, 9 | ResponseSchema extends ZodSchema = any, 10 | PayloadSchema extends ZodSchema = any 11 | >( 12 | url: string, 13 | config?: 14 | | (Omit & { 15 | method?: Method; 16 | params?: z.infer; 17 | payloadSchema?: PayloadSchema; 18 | responseSchema?: ResponseSchema; 19 | paramsSchema?: ParamsSchema; 20 | safeParse?: boolean; 21 | baseURL?: string; 22 | }) 23 | | undefined 24 | ) { 25 | return async () => { 26 | if (config?.safeParse && config.payloadSchema) { 27 | const safePayload = config.payloadSchema.safeParse(config.data); 28 | invariant(safePayload.success, 'Invalid payload'); 29 | } 30 | 31 | if (config?.safeParse && config.paramsSchema) { 32 | const safeParams = config.paramsSchema.safeParse(config.params); 33 | invariant(safeParams.success, 'Invalid Schema'); 34 | } 35 | 36 | const response = await http(url, { 37 | ...config, 38 | method: config?.method || 'GET', 39 | }); 40 | 41 | if (config?.safeParse && config.responseSchema) { 42 | const safeResponse = config.responseSchema.safeParse(response.data); 43 | invariant(safeResponse.success, 'Not relevent result from server'); 44 | return { 45 | statusCode: safeResponse.data, 46 | result: response.data, 47 | } as { statusCode: number; result: z.infer }; 48 | } 49 | 50 | return { 51 | statusCode: response.status, 52 | result: response.data, 53 | } as { 54 | statusCode: number; 55 | result: z.infer; 56 | }; 57 | }; 58 | } 59 | 60 | export default zodSafeQuery; 61 | -------------------------------------------------------------------------------- /backend/templates/base/navbar.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | -------------------------------------------------------------------------------- /frontend/src/utils/datetime.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment-timezone'; 2 | 3 | export const DATE_FORMAT = { 4 | DATETIME_GENERAL: 'YYYY, MMMM Do, h:mm:ss a', 5 | DATETIME_COUNTDOWN: 'YYYY-MM-DD HH:mm', 6 | DATETIME_YY_MM_DD: 'YYYY-MM-DD', 7 | }; 8 | 9 | export const formatDateTime = (serverDatetime: any, format: string) => { 10 | return moment.tz(serverDatetime, 'Asia/Dhaka').format(format); 11 | }; 12 | 13 | export const daysBetweenDates = (startDate: Date, endDate: Date = new Date()) => { 14 | return moment(endDate).diff(startDate, 'days'); 15 | }; 16 | 17 | export const getStartOfCurrentDate = (date: Date) => { 18 | let currentDate = null; 19 | 20 | if (!date) currentDate = new Date(); 21 | else currentDate = new Date(date); 22 | 23 | currentDate.setHours(0, 0, 0, 0); 24 | 25 | return currentDate; 26 | }; 27 | 28 | export const getEndOfCurrentDate = (date: Date) => { 29 | let currentDate = null; 30 | 31 | if (!date) currentDate = new Date(); 32 | else currentDate = new Date(date); 33 | 34 | currentDate.setHours(23, 59, 59, 999); 35 | 36 | return currentDate; 37 | }; 38 | 39 | export const isSameDate = (date1: Date, date2: Date) => { 40 | const formattedDate1 = moment.tz(date1, 'Asia/Dhaka').format('YYYY-MM-DD').split('-')[2]; 41 | 42 | const formattedDate2 = moment.tz(date2, 'Asia/Dhaka').format('YYYY-MM-DD').split('-')[2]; 43 | 44 | return formattedDate1 === formattedDate2; 45 | }; 46 | 47 | export const getFormattedCurrentDatetime = (format: string) => moment().tz('Asia/Dhaka').format(format); 48 | 49 | export const isDateExpired = (date: Date) => { 50 | var dateToCompare = moment(date); 51 | var now = moment(); 52 | 53 | if (now > dateToCompare) { 54 | return true; 55 | } else { 56 | return false; 57 | } 58 | }; 59 | 60 | export const isDateLess = (then: Date, now: Date) => { 61 | var before = moment(then); 62 | var after = moment(now); 63 | 64 | console.log('isDateLess'); 65 | 66 | if (after < before) return true; 67 | 68 | return false; 69 | }; 70 | 71 | export const addDays = (date: Date, days: number) => { 72 | const result = new Date(date); 73 | result.setDate(result.getDate() + days); 74 | 75 | return result; 76 | }; 77 | -------------------------------------------------------------------------------- /frontend/src/components/Pagination/Pagination.tsx: -------------------------------------------------------------------------------- 1 | import { PreviousIcon } from '@/icons'; 2 | import NextIcon from '@/icons/NextIcon'; 3 | import { Button } from '@chakra-ui/react'; 4 | import ReactPaginate from 'react-paginate'; 5 | interface IPaginationProps { 6 | pageCount: number; 7 | pageRangeDisplayed?: number; 8 | handlePageChange: (event: { selected: number }) => void; 9 | currentPageNumber: number; 10 | handleClickPreviosPage?: () => void; 11 | handleClickNextPage?: () => void; 12 | } 13 | 14 | const Pagination: React.FC = ({ 15 | pageCount, 16 | currentPageNumber, 17 | handlePageChange, 18 | pageRangeDisplayed = 5, 19 | handleClickPreviosPage, 20 | handleClickNextPage, 21 | }) => { 22 | // const [pageNumber, setPageNumber] = useState(0); 23 | // const handlePageChange = (event: { selected: number }) => { 24 | // setPageNumber(event.selected); 25 | // console.log('OFFSET->', event.selected * 2); 26 | // setCurrentPageNumber(event.selected); 27 | // }; 28 | return ( 29 |
30 | 33 | 46 | 49 |
50 | ); 51 | }; 52 | 53 | export default Pagination; 54 | -------------------------------------------------------------------------------- /backend/templates/transaction/loan_disbursement_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base/base.html' %} 2 | 3 | {% block extrahead %} 4 | 5 | 6 | {% endblock %} 7 | 8 | 9 | {% block content %} 10 | 11 |
12 |
13 | কর্জ প্রদান 14 |
15 |
16 |
17 |
18 |
19 | {% csrf_token %} 20 | {{ form.as_p }} 21 | 22 |
23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
সদস্যের নাম{{ member.name }}
অভিভাবকের নাম{{ member.guardian_name }}
দলের নাম{{ member.team }}
সিরিয়াল নম্বর{{ member.serial_number }}
সঞ্চয় স্থিতি{{ member.balance }}
50 |
51 |
52 | 53 |
54 |
55 | 56 |
57 | 58 | 59 |
60 | 61 | {% endblock %} 62 | 63 | {% block script %} 64 | 65 | 72 | {% endblock %} -------------------------------------------------------------------------------- /backend/templates/base/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% include 'base/header.html' %} 7 | {% block extrahead %} {% endblock %} 8 | 9 | 36 | 37 | 38 | 39 | {% include 'base/navbar.html' %} 40 | 41 |
42 | 43 | {% include 'base/sidebar.html' %} 44 | 45 | 46 |
47 | 48 | 49 | 50 |
51 |
52 | {% if messages %} 53 |
54 | {% for message in messages %} 55 | 58 | {% endfor %} 59 |
60 | {% endif %} 61 | {% block content %} 62 | 63 | {% endblock %} 64 |
65 | 66 |
67 | 68 |
69 | 70 | 71 | 72 | {% include 'base/asidebar.html' %} 73 | 74 |
75 | 76 | 77 | {% include 'base/footer.html' %} 78 | 79 | 80 | {% include 'base/js.html' %} 81 | 82 | {% block script %} 83 | 84 | {% endblock %} 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-v2", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "format": "prettier --write \"**/*.{ts,tsx,md,json}\"" 11 | }, 12 | "dependencies": { 13 | "@chakra-ui/react": "^2.5.5", 14 | "@emotion/react": "^11.10.6", 15 | "@emotion/styled": "^11.10.6", 16 | "@hookform/resolvers": "^3.0.0", 17 | "@react-pdf/renderer": "^3.1.12", 18 | "@tanstack/react-query": "^4.28.0", 19 | "@tanstack/react-query-devtools": "^4.28.0", 20 | "@types/node": "18.15.11", 21 | "@types/react": "18.0.32", 22 | "@types/react-dom": "18.0.11", 23 | "axios": "^1.3.4", 24 | "cookie": "^0.5.0", 25 | "dayjs": "^1.11.7", 26 | "eslint": "^8.37.0", 27 | "eslint-config-next": "^13.2.4", 28 | "framer-motion": "^10.10.0", 29 | "jsonwebtoken": "^9.0.0", 30 | "moment-timezone": "^0.5.43", 31 | "next": "13.2.4", 32 | "next-auth": "^4.22.1", 33 | "next-translate": "^2.0.4", 34 | "react": "18.2.0", 35 | "react-datepicker": "^4.12.0", 36 | "react-dom": "18.2.0", 37 | "react-hook-form": "^7.43.9", 38 | "react-icons": "^4.9.0", 39 | "react-paginate": "^8.1.5", 40 | "recharts": "^2.5.0", 41 | "sass": "^1.60.0", 42 | "sweetalert2": "^11.6.13", 43 | "tiny-invariant": "^1.3.1", 44 | "typescript": "^5.0.3", 45 | "yarn": "^1.22.19", 46 | "zod": "^3.21.4", 47 | "zustand": "^4.3.7" 48 | }, 49 | "devDependencies": { 50 | "@types/cookie": "^0.5.1", 51 | "@types/jsonwebtoken": "^9.0.1", 52 | "@types/react-datepicker": "^4.11.2", 53 | "@typescript-eslint/eslint-plugin": "^5.57.0", 54 | "@typescript-eslint/parser": "^5.57.0", 55 | "autoprefixer": "^10.4.14", 56 | "eslint-config-prettier": "^8.8.0", 57 | "eslint-plugin-react": "^7.32.2", 58 | "next-translate-plugin": "^2.0.4", 59 | "postcss": "^8.4.21", 60 | "prettier": "^2.8.7", 61 | "prettier-plugin-organize-imports": "^3.2.2", 62 | "prettier-plugin-tailwindcss": "^0.2.6", 63 | "tailwindcss": "^3.3.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /backend/peoples/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from django.db import models 3 | from django.core.exceptions import ValidationError 4 | 5 | from journal.models import GeneralJournal 6 | from transaction.models import Loan 7 | 8 | STAFF_ROLES = (("cl", "Collector"), ("bw", "Branch Owner")) 9 | 10 | 11 | class Staff(models.Model): 12 | """Like as profile of a Staff user""" 13 | 14 | name = models.CharField(max_length=150) 15 | mobile_number = models.CharField(max_length=11, blank=True, null=True) 16 | email = models.EmailField(blank=True, null=True) 17 | user = models.OneToOneField( 18 | "organization.User", on_delete=models.SET_NULL, blank=True, null=True 19 | ) 20 | 21 | def __str__(self): 22 | return self.name 23 | 24 | 25 | GENDER_CHOICES = ( 26 | ("male", "Male"), 27 | ("female", "Female"), 28 | ) 29 | 30 | 31 | class Member(models.Model): 32 | name = models.CharField(max_length=150) 33 | mobile_number = models.CharField(max_length=11, blank=True, null=True) 34 | nid_number = models.CharField(max_length=25, blank=True, null=True) 35 | guardian_name = models.CharField(max_length=150, blank=True, null=True) 36 | gender = models.CharField(max_length=6, choices=GENDER_CHOICES, default="male") 37 | serial_number = models.IntegerField(default=1) 38 | uuid = models.UUIDField(default=uuid.uuid4, editable=False) 39 | team = models.ForeignKey("organization.Team", on_delete=models.RESTRICT) 40 | branch = models.ForeignKey("organization.Branch", on_delete=models.RESTRICT) 41 | is_active = models.BooleanField(default=True) 42 | created_at = models.DateTimeField(auto_now_add=True) 43 | updated_at = models.DateTimeField(auto_now=True) 44 | 45 | class Meta: 46 | unique_together = ("team", "serial_number") 47 | 48 | def clean(self): 49 | super().clean() 50 | if self.serial_number > 25: 51 | raise ValidationError("Serial number must not be greater than 25.") 52 | 53 | def __str__(self): 54 | return self.name 55 | 56 | def balance(self): 57 | return GeneralJournal.objects.get_member_balance(self) 58 | 59 | def has_active_loan(self): 60 | return Loan.objects.filter(member=self, is_paid=False).exists() 61 | 62 | def get_my_loan(self): 63 | try: 64 | return Loan.objects.get(member=self, is_paid=False) 65 | except: 66 | return None 67 | 68 | -------------------------------------------------------------------------------- /backend/report/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from organization.models import BaseModel 4 | 5 | 6 | class CIHManager(models.Manager): 7 | def last_day_cash_in_hand(self, branch, date): 8 | """ 9 | Last day cash in hand of a branch 10 | """ 11 | last_cih_obj = CIHCalculation.objects.filter(branch=branch, date__lt=date) 12 | print("last cih", last_cih_obj) 13 | if last_cih_obj: 14 | return last_cih_obj.latest("date").cash_in_hand 15 | return 0 16 | 17 | def add_cash_in_hand(self, branch, date, amount): 18 | """ 19 | Add cash in hand for income 20 | """ 21 | last_day_cih = 0 22 | cih, is_created = CIHCalculation.objects.get_or_create( 23 | organization=branch.organization, branch=branch, date=date 24 | ) 25 | print("is creatd", is_created) 26 | if is_created: 27 | # if first time of a date 28 | last_day_cih = CIHCalculation.objects.last_day_cash_in_hand(branch, date) 29 | 30 | cih.cash_in_hand = cih.cash_in_hand + amount + last_day_cih 31 | cih.save() 32 | 33 | def deduct_cash_in_hand(self, branch, date, amount): 34 | """ 35 | deduct cash in hand for expense 36 | """ 37 | last_day_cih = 0 38 | cih, is_created = CIHCalculation.objects.get_or_create( 39 | organization=branch.organization, branch=branch, date=date 40 | ) 41 | if is_created: 42 | # if first time of a date 43 | last_day_cih = CIHCalculation.objects.last_day_cash_in_hand( 44 | branch, date=date 45 | ) 46 | 47 | cih.cash_in_hand = cih.cash_in_hand - amount + last_day_cih 48 | cih.save() 49 | 50 | 51 | class CIHCalculation(BaseModel): 52 | date = models.DateField() 53 | cash_in_hand = models.IntegerField(default=0) 54 | # income 55 | # admission_fee = models.IntegerField(default=0) 56 | # deposit_collection = models.IntegerField(default=0) 57 | # installment_collection = models.IntegerField(default=0) 58 | # others_income = models.IntegerField(default=0) 59 | # # expense 60 | # loan_distribution = models.IntegerField(default=0) 61 | # deposit_return = models.IntegerField(default=0) 62 | # others_cost = models.IntegerField(default=0) 63 | 64 | objects = CIHManager() 65 | 66 | def __str__(self): 67 | return str(self.pk) 68 | -------------------------------------------------------------------------------- /backend/organization/management/commands/prepare_org.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.contrib.auth.models import User 3 | 4 | from organization.models import ( 5 | Organization, 6 | User, 7 | Branch, 8 | Division, 9 | District, 10 | Thana, 11 | Team, 12 | ) 13 | from transaction.models import TransactionCategory 14 | from peoples.models import Staff 15 | 16 | class Command(BaseCommand): 17 | help = "Create a new user, category, and post" 18 | 19 | def add_arguments(self, parser): 20 | pass 21 | 22 | def handle(self, *args, **options): 23 | TransactionCategory.objects.get_or_create(name='Registration Fee') 24 | TransactionCategory.objects.get_or_create(name='Office Rent') 25 | # Create new division 26 | division, _ = Division.objects.get_or_create(name="Test Division") 27 | 28 | # Create new district 29 | district, _ = District.objects.get_or_create( 30 | name="Test District", division=division 31 | ) 32 | 33 | # Create new thana 34 | thana, _ = Thana.objects.get_or_create(name="Test Thana", district=district) 35 | 36 | # Create new organization 37 | org, _ = Organization.objects.get_or_create(name="Test Org", code=111) 38 | 39 | # Create new branch 40 | branch, _ = Branch.objects.get_or_create( 41 | name="Test Branch", 42 | code=111, 43 | organization=org, 44 | # thana=thana, 45 | ) 46 | 47 | # Create new user 48 | user, created = User.objects.get_or_create(username="admin") 49 | user.set_password("admin") 50 | user.is_superuser = True 51 | user.is_staff = True 52 | user.branch = branch 53 | user.role = 'BO' 54 | user.save() 55 | 56 | # Create new staff 57 | staff, _ = Staff.objects.get_or_create( 58 | name="Staff User", 59 | mobile_number="01111111111", 60 | email="staff@mail.com", 61 | user=user, 62 | ) 63 | 64 | team, created = Team.objects.get_or_create( 65 | name="Demo Team", branch=branch, defaults={"owner": user} 66 | ) 67 | 68 | self.stdout.write( 69 | self.style.SUCCESS( 70 | f"Successfully created organization '{org.name}', branch '{branch.name}', user '{user.username}', staff '{staff.name}' team '{team.name}'" 71 | ) 72 | ) 73 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 18 | 19 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 20 | 21 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 22 | 23 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 24 | 25 | ## One Importent note: About Environment Variable 26 | 27 | #Some time after cloning and running on local it shows an error that indicate there is no environment setup because there is no .env file in your project (Frontend).At that situation all you have to do is: 28 | 29 | 1. after cloning and change directory to frontend folder and open with vs 30 | code 31 | 2. run => npm install 32 | 3. look there is an .env.example file just copy the whole code inside this 33 | file 34 | 4. create .env file in root and paste that copied code in .env file 35 | 5. then run npm run start 36 | 37 | hope, it will work. 38 | 39 | ## Learn More 40 | 41 | To learn more about Next.js, take a look at the following resources: 42 | 43 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 44 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 45 | 46 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 47 | 48 | ## Deploy on Vercel 49 | 50 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 51 | 52 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 53 | -------------------------------------------------------------------------------- /backend/transaction/utils.py: -------------------------------------------------------------------------------- 1 | from .models import Savings, Installment 2 | from journal.models import GeneralJournal 3 | 4 | 5 | def format_savings_date(member, month): 6 | balance = 0 7 | member_savings = GeneralJournal.objects.filter(member=member, accounts__code='DE') 8 | current_month_savings = member_savings.filter(date__month=month) 9 | balance = GeneralJournal.objects.get_member_balance(member) 10 | 11 | # If no current month savings data, get last savings balance 12 | # if not current_month_savings: 13 | # last_savings = member_savings.last() 14 | # if last_savings: 15 | # balance = last_savings.balance 16 | d = { 17 | "sl": member.serial_number, 18 | "member_id": member.id, 19 | "member_name": member.name, 20 | "guardian_name": member.guardian_name, 21 | "balance": balance, 22 | "week1": 0, 23 | "week2": 0, 24 | "week3": 0, 25 | "week4": 0, 26 | } 27 | 28 | # Format current month savings data as week1, week2, week3, week4 29 | for savings in current_month_savings: 30 | # Get saving posting date int. 31 | savings_date = savings.date.day 32 | if savings_date <= 7: 33 | d["week1"] = savings.credit 34 | elif 7 < savings_date <= 14: 35 | d["week2"] = savings.credit 36 | elif 14 < savings_date <= 21: 37 | d["week3"] = savings.credit 38 | elif 21 < savings_date <= 31: 39 | d["week4"] = savings.credit 40 | return d 41 | 42 | 43 | def format_loan_data(loan, month): 44 | member = loan.member 45 | d = { 46 | "sl": member.serial_number, 47 | "member_id": member.id, 48 | "member_name": member.name, 49 | "guardian_name": member.guardian_name, 50 | "loan_id": loan.id, 51 | "loan_amount": loan.amount, 52 | "loan_balance": loan.total_due, 53 | "week1": 0, 54 | "week2": 0, 55 | "week3": 0, 56 | "week4": 0, 57 | } 58 | 59 | # Format current month installment 60 | installments = Installment.objects.filter(loan=loan, date__month=month) 61 | for installment in installments: 62 | # Get saving posting date int. 63 | installment_date = installment.date.day 64 | if installment_date <= 7: 65 | d["week1"] = installment.amount 66 | elif 7 < installment_date <= 14: 67 | d["week2"] = installment.amount 68 | elif 14 < installment_date <= 21: 69 | d["week3"] = installment.amount 70 | elif 21 < installment_date <= 31: 71 | d["week4"] = installment.amount 72 | return d 73 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const colors = require('tailwindcss/colors'); 4 | 5 | module.exports = { 6 | content: [ 7 | './pages/**/*.{js,ts,jsx,tsx}', 8 | './pages/*.{js,ts,jsx,tsx}', 9 | './src/layouts/**/*.{js,ts,jsx,tsx}', 10 | './src/modules/**/*.{js,ts,jsx,tsx}', 11 | './src/**/*.{js,ts,jsx,tsx}', 12 | ], 13 | darkMode: 'class', 14 | theme: { 15 | extend: {}, 16 | fontFamily: { 17 | noto: ['noto', 'sans-serif'], 18 | nikosh: ['Nikosh', 'sans-serif'], 19 | }, 20 | colors: { 21 | transparent: 'transparent', 22 | current: 'currentColor', 23 | black: colors.black, 24 | white: colors.white, 25 | red: colors.red, 26 | error: { 27 | 25: '#FFFBFA', 28 | 50: '#FEF3F2', 29 | 100: '#FEE4E2', 30 | 200: '#FECDCA', 31 | 300: '#FDA29B', 32 | 400: '#F97066', 33 | 500: '#F04438', 34 | 600: '#D92D20', 35 | 700: '#B42318', 36 | 800: '#912018', 37 | 900: '#7A271A', 38 | }, 39 | brand: { 40 | 25: '#F6FEF9', 41 | 50: '#ECFDF3', 42 | 100: '#D1FADF', 43 | 200: '#A6F4C5', 44 | 300: '#6CE9A6', 45 | 400: '#32D583', 46 | 500: '#12B76A', 47 | 600: '#039855', 48 | 700: '#027A48', 49 | 800: '#05603A', 50 | 900: '#054F31', 51 | }, 52 | gray: { 53 | 25: '#FCFCFD', 54 | 50: '#F9FAFB', 55 | 100: '#F2F4F7', 56 | 200: '#EAECF0', 57 | 300: '#D0D5DD', 58 | 400: '#98A2B3', 59 | 500: '#667085', 60 | 600: '#475467', 61 | 700: '#344054', 62 | 800: '#1D2939', 63 | 900: '#101828', 64 | }, 65 | warning: { 66 | 25: '#FFFCF5', 67 | 50: '#FFFCF5', 68 | 100: '#FEF0C7', 69 | 200: '#FEDF89', 70 | 300: '#FEC84B', 71 | 400: '#FDB022', 72 | 500: '#F79009', 73 | 600: '#DC6803', 74 | 700: '#B54708', 75 | 800: '#93370D', 76 | 900: '#7A2E0E', 77 | }, 78 | }, 79 | }, 80 | plugins: [], 81 | }; 82 | -------------------------------------------------------------------------------- /backend/peoples/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import get_object_or_404 2 | from django.db.models import Sum 3 | from rest_framework import viewsets 4 | from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView 5 | from rest_framework.permissions import IsAuthenticated 6 | from rest_framework.filters import SearchFilter, OrderingFilter 7 | from rest_framework.response import Response 8 | from rest_framework import status 9 | from rest_framework.views import APIView 10 | from django_filters.rest_framework import DjangoFilterBackend 11 | from transaction.models import Loan, Savings 12 | 13 | # App related 14 | from peoples.models import Member, Staff 15 | from peoples.permissions import IsSameBranch 16 | from peoples.serializers import ( 17 | MemberCreateSerializer, 18 | MemberDetailSerializer, 19 | MemberSavingsLoanInfoSerializer, 20 | ) 21 | 22 | 23 | class MemberListCreateView(ListCreateAPIView): 24 | permission_classes = [IsAuthenticated] 25 | filter_backends = [DjangoFilterBackend, SearchFilter] 26 | filterset_fields = ["team", "branch", "is_active", "gender"] 27 | search_fields = ["name", "mobile_number"] 28 | 29 | def get_queryset(self): 30 | return Member.objects.filter(branch=self.request.user.branch) 31 | 32 | def perform_create(self, serializer): 33 | serializer.save(branch=self.request.user.branch) 34 | 35 | def get_serializer_class(self): 36 | if self.request.method == "POST": 37 | return MemberCreateSerializer 38 | return MemberDetailSerializer 39 | 40 | 41 | class MemberDetailsView(RetrieveUpdateDestroyAPIView): 42 | permission_classes = [IsAuthenticated, IsSameBranch] 43 | serializer_class = MemberDetailSerializer 44 | 45 | 46 | # def get_queryset(self): 47 | # return Member.objects.filter(branch=self.request.user.branch) 48 | 49 | def get_object(self): 50 | return get_object_or_404(Member, id=self.kwargs.get("id")) 51 | 52 | 53 | 54 | class MemberSavingLoanInfo(APIView): 55 | serializer_class = MemberSavingsLoanInfoSerializer 56 | 57 | def get(self, request, *args, **kwargs): 58 | member = get_object_or_404(Member, id=kwargs.get('id')) 59 | savings=Savings.objects.filter(member=member).aggregate(Sum('amount'))['amount__sum'] 60 | last_loan = Loan.objects.filter(member=member).last() 61 | total_loan = Loan.objects.filter(member=member).count() 62 | 63 | data = { 64 | "total_savings": savings if savings else 0, 65 | "last_loan": last_loan.amount if last_loan else 0, 66 | "loan_date": last_loan.date if last_loan else None, 67 | "loan_paid": last_loan.total_paid if last_loan else 0, 68 | "installment_paid": last_loan.installment_paid if last_loan else 0, 69 | "total_loan_count": total_loan 70 | } 71 | serializer = self.serializer_class(data) 72 | return Response(serializer.data, status=status.HTTP_200_OK) -------------------------------------------------------------------------------- /frontend/src/env.mjs: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | /** 4 | * Specify your server-side environment variables schema here. 5 | * This way you can ensure the app isn't built with invalid env vars. 6 | */ 7 | const server = z.object({ 8 | NODE_ENV: z.enum(['development', 'test', 'production']), 9 | }); 10 | 11 | /** 12 | * Specify your client-side environment variables schema here. 13 | * This way you can ensure the app isn't built with invalid env vars. 14 | * To expose them to the client, prefix them with `NEXT_PUBLIC_`. 15 | */ 16 | const client = z.object({ 17 | NEXT_PUBLIC_BACKEND_API_URL: z.string().url(), 18 | NEXT_PUBLIC_NODE_ENV: z.enum(['development', 'test', 'production']), 19 | }); 20 | 21 | /** 22 | * You can't destruct `process.env` as a regular object in the Next.js 23 | * edge runtimes (e.g. middlewares) or client-side so we need to destruct manually. 24 | * @type {Record | keyof z.infer, string | undefined>} 25 | */ 26 | const processEnv = { 27 | NEXT_PUBLIC_BACKEND_API_URL: process.env.NEXT_PUBLIC_BACKEND_API_URL, 28 | NODE_ENV: process.env.NODE_ENV, 29 | NEXT_PUBLIC_NODE_ENV: process.env.NODE_ENV, 30 | }; 31 | 32 | // Don't touch the part below 33 | // -------------------------- 34 | 35 | const merged = server.merge(client); 36 | /** @type z.infer 37 | * @ts-ignore - can't type this properly in jsdoc */ 38 | let env = process.env; 39 | 40 | if (!!process.env.SKIP_ENV_VALIDATION == false) { 41 | const isServer = typeof window === 'undefined'; 42 | 43 | const parsed = isServer 44 | ? merged.safeParse(processEnv) // on server we can validate all env vars 45 | : client.safeParse(processEnv); // on client we can only validate the ones that are exposed 46 | 47 | if (parsed.success === false) { 48 | // eslint-disable-next-line no-console 49 | console.error('❌ Invalid environment variables:', parsed.error.flatten().fieldErrors); 50 | throw new Error('Invalid environment variables'); 51 | } 52 | 53 | /** @type z.infer 54 | * @ts-ignore - can't type this properly in jsdoc */ 55 | // eslint-disable-next-line no-undef 56 | env = new Proxy(parsed.data, { 57 | get(target, prop) { 58 | if (typeof prop !== 'string') return undefined; 59 | // Throw a descriptive error if a server-side env var is accessed on the client 60 | // Otherwise it would just be returning `undefined` and be annoying to debug 61 | if (!isServer && !prop.startsWith('NEXT_PUBLIC_')) 62 | throw new Error( 63 | process.env.NODE_ENV === 'production' 64 | ? '❌ Attempted to access a server-side environment variable on the client' 65 | : `❌ Attempted to access server-side environment variable '${prop}' on the client` 66 | ); 67 | 68 | return target[prop]; 69 | }, 70 | }); 71 | } 72 | 73 | export { env }; 74 | -------------------------------------------------------------------------------- /backend/templates/transaction/deposit_posting.html: -------------------------------------------------------------------------------- 1 | {% extends 'base/base.html' %} 2 | 3 | {% block extrahead %} 4 | 5 | 6 | {% endblock %} 7 | 8 | 9 | {% block content %} 10 | 11 |
12 |
13 | Deposit Posting 14 |
15 |
16 |
17 |
18 |
19 | {{ member_choice_form }} 20 | 21 |
22 |
23 |
24 | Next 25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 | {% if member %} 37 |
38 | {% csrf_token %} 39 | {{ deposit_form.as_p }} 40 | 41 |
42 | {% else %} 43 |

কোন সদস্য নেই

44 | {% endif %} 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
সদস্যের নাম{{ member.name }}
অভিভাবকের নাম{{ member.guardian_name }}
দলের নাম{{ member.team }}
সিরিয়াল নম্বর{{ member.serial_number }}
সঞ্চয় স্থিতি{{ member.balance }}
72 |
73 |
74 | 75 |
76 |
77 | 78 |
79 | 80 | 81 |
82 | 83 | {% endblock %} 84 | 85 | {% block script %} 86 | 87 | 94 | {% endblock %} -------------------------------------------------------------------------------- /frontend/src/components/DashboardNavbar/index.tsx: -------------------------------------------------------------------------------- 1 | import isRouterPathnameMatched from '@/utils/isRouterPathnameMatched'; 2 | import { Avatar, Button } from '@chakra-ui/react'; 3 | import { signOut } from 'next-auth/react'; 4 | import Link from 'next/link'; 5 | import { useRouter } from 'next/router'; 6 | 7 | const navItems = [ 8 | { 9 | name: 'Dashbaord', 10 | href: '/dashboard', 11 | exact: true, 12 | }, 13 | { 14 | name: 'Branch', 15 | href: '/branch', 16 | exact: false, 17 | }, 18 | // { 19 | // name: 'Team', 20 | // href: '/team', 21 | // exact: false, 22 | // }, 23 | { 24 | name: 'Withdraw', 25 | href: '/withdraw', 26 | exact: false, 27 | }, 28 | { 29 | name: 'Accounts', 30 | href: '/accounts', 31 | exact: false, 32 | }, 33 | ]; 34 | 35 | const DashboardNavbar = () => { 36 | // const { data: session, status } = useSession(); 37 | const router = useRouter(); 38 | 39 | //handling Logout 40 | const handleRedirectToLogin = () => { 41 | signOut({ callbackUrl: '/login' }); 42 | }; 43 | 44 | return ( 45 |
46 |
47 |
48 | 49 |

Qard al-Hasan

50 | 51 |
52 | {navItems.map((item) => { 53 | return ( 54 | 55 | 66 | 67 | ); 68 | })} 69 |
70 |
71 |
72 | 73 | 74 | Log Out 75 | 76 |
77 |
78 |
79 | ); 80 | }; 81 | 82 | export default DashboardNavbar; 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # কর্জে হাসানা বাংলাদেশ 2 | 3 | ## কর্জে হাসানা কী? 4 | 5 | কর্জে হাসানা হচ্ছে সুদমুক্ত ঋণ। আপনাকে আমরা যে পরিমান কর্জ দিবো আপনি ঠিক তত টাকাই ফেরত দিবেন ধাপে ধাপে (কিস্তিতে), কোন সার্ভিস চার্জ নেই। 6 | 7 | ## আমাদের লক্ষ্য 8 | 9 | আমরা এমন একটি প্লাটফর্ম তৈরী করতে চাই যাতে এই প্লাটফর্ম ব্যাবহার করে দেশের যে কোন প্রান্তে যে কেউ কর্জে হাসানা প্রজেক্ট পরিচালনা করতে পারেন। 10 | [প্রজেক্ট সম্পর্কে বিস্তারিত](https://docs.google.com/document/d/1GiPgOxU19B5d-DzzesWb6RXrlbmu5xDvSKjMW0vDsC8/edit?usp=sharing) 11 | 12 | # Qard al-Hasan 13 | 14 | ## What is Qard al-Hasan? 15 | 16 | Qard al-Hasana is an interest free loan. You will pay back exactly the amount we lend you in installments (instalments), no service charges. 17 | 18 | ## Our Goal 19 | 20 | We want to create such a platform so that anyone can manage Qard al-Hasana project in any part of the country using this platform. 21 | [details about the project](https://docs.google.com/document/d/1GiPgOxU19B5d-DzzesWb6RXrlbmu5xDvSKjMW0vDsC8/edit?usp=sharing) 22 | 23 | # INSTALLATION 24 | 25 | Clone the project to your local machine. 26 | 27 | ## Frontend 28 | 29 | Change directory into the "frontend" folder and create a .env file. Copy and paste the codes from the .env.examples file into the .env file. 30 | 31 | Install dependencies: 32 | 33 | - Using Yarn (recommended) - `yarn` 34 | - Using npm - `npm i` 35 | 36 | Start the development server: 37 | 38 | - Using Yarn (recommended) - `yarn dev` 39 | - Using npm - `npm run dev` 40 | 41 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 42 | 43 | ## Backend 44 | 45 | ### Run backend in a virtual environment 46 | 47 | Change directory into the "backend" folder and create a .env file. Copy and paste the codes from the .env.examples file into the .env file. 48 | 49 | Create virtualenv: 50 | 51 | ``` 52 | python3 -m venv 53 | ``` 54 | 55 | Activate the virtualenv: 56 | 57 | - Linux/Mac - `source /bin/activate` 58 | - Windows - `\Scripts\Activate` 59 | 60 | Install dependencies, migrate database and run the project: 61 | 62 | ``` 63 | pip install -r requirements.txt 64 | python manage.py migrate 65 | python manage.py prepare_org 66 | python manage.py runserver 67 | ``` 68 | 69 | Activate pre-commit hook: 70 | 71 | ``` 72 | pre-commit install 73 | ``` 74 | 75 | ### Run backend with docker 76 | 77 | ``` 78 | cd backend 79 | docker compose up 80 | ``` 81 | 82 | ### API doc: 83 | 84 | [localhost:8000](http://localhost:8000/) 85 | get access token: [localhost:8000/api/v1/auth/login/](localhost:8000/api/v1/auth/login/) 86 | username: admin 87 | password: admin 88 | copy the access token and use "Authorize" buton for token authentication. Now you can access other API. 89 | 90 | ## How to contribute 91 | 92 | About this project: [link](https://docs.google.com/document/d/1GiPgOxU19B5d-DzzesWb6RXrlbmu5xDvSKjMW0vDsC8/edit?usp=sharing) 93 | Task list: [GitHub Link](https://github.com/users/harunurkst/projects/1) 94 | Join to our Discord channel: [Invitation link](https://discord.gg/9dWdP8zM4K) 95 | -------------------------------------------------------------------------------- /backend/static/src/scss/core/_navbar.scss: -------------------------------------------------------------------------------- 1 | .app-header.navbar { 2 | position: relative; 3 | flex-direction: row; 4 | height: $navbar-height; 5 | padding: 0; 6 | margin: 0; 7 | background-color: $navbar-bg; 8 | @include borders($navbar-border); 9 | 10 | .navbar-brand { 11 | display: inline-block; 12 | width: $navbar-brand-width; 13 | height: $navbar-height; 14 | padding: $navbar-padding-y $navbar-padding-x; 15 | margin-right: 0; 16 | background-color: $navbar-brand-bg; 17 | background-image: $navbar-brand-logo; 18 | background-repeat: no-repeat; 19 | background-position: center center; 20 | background-size: $navbar-brand-logo-size; 21 | @include borders($navbar-brand-border); 22 | } 23 | 24 | .navbar-toggler { 25 | min-width: 50px; 26 | padding: $navbar-toggler-padding-y 0; 27 | 28 | &:hover .navbar-toggler-icon { 29 | background-image: $navbar-toggler-icon-hover; 30 | } 31 | } 32 | 33 | .navbar-toggler-icon { 34 | height: 23px; 35 | background-image: $navbar-toggler-icon; 36 | } 37 | 38 | .navbar-nav { 39 | flex-direction: row; 40 | align-items: center; 41 | } 42 | 43 | .nav-item { 44 | position: relative; 45 | min-width: 50px; 46 | margin: 0 !important; 47 | text-align: center; 48 | 49 | button { 50 | margin: 0 auto; 51 | } 52 | 53 | .nav-link { 54 | padding-top: 0; 55 | padding-bottom: 0; 56 | background: 0; 57 | border: 0; 58 | 59 | .badge { 60 | position: absolute; 61 | top: 50%; 62 | left: 50%; 63 | margin-top: -16px; 64 | margin-left: 0; 65 | } 66 | 67 | > .img-avatar { 68 | height: $navbar-height - 20px; 69 | margin: 0 10px; 70 | } 71 | } 72 | } 73 | 74 | .dropdown-menu { 75 | padding-bottom: 0; 76 | line-height: $line-height-base; 77 | } 78 | 79 | .dropdown-item { 80 | min-width: 180px; 81 | } 82 | } 83 | 84 | .navbar-brand { 85 | color: $navbar-active-color; 86 | 87 | @include hover-focus { 88 | color: $navbar-active-color; 89 | } 90 | } 91 | 92 | .navbar-nav { 93 | .nav-link { 94 | color: $navbar-color; 95 | 96 | @include hover-focus { 97 | color: $navbar-hover-color; 98 | } 99 | } 100 | 101 | .open > .nav-link, 102 | .active > .nav-link, 103 | .nav-link.open, 104 | .nav-link.active { 105 | @include plain-hover-focus { 106 | color: $navbar-active-color; 107 | } 108 | } 109 | } 110 | 111 | .navbar-divider { 112 | background-color: rgba(0,0,0,.075); 113 | } 114 | 115 | @include media-breakpoint-up(lg) { 116 | .brand-minimized { 117 | .app-header.navbar { 118 | .navbar-brand { 119 | width: $navbar-brand-minimized-width; 120 | background-color: $navbar-brand-minimized-bg; 121 | background-image: $navbar-brand-minimized-logo; 122 | background-size: $navbar-brand-minimized-logo-size; 123 | @include borders($navbar-brand-minimized-border); 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /frontend/pages/member/installmentGraph.tsx: -------------------------------------------------------------------------------- 1 | import { Bar, BarChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; 2 | 3 | const InstallMentGraph = (props) => { 4 | const data = [ 5 | { 6 | name: 'Jan', 7 | week1: 1000, 8 | week2: 50, 9 | week3: 100, 10 | week4: 30, 11 | }, 12 | { 13 | name: 'Feb', 14 | week1: 20, 15 | week2: 80, 16 | week3: 100, 17 | week4: 30, 18 | }, 19 | { 20 | name: 'Mar', 21 | week1: 20, 22 | week2: 50, 23 | week3: 100, 24 | week4: 30, 25 | }, 26 | { 27 | name: 'Apr', 28 | week1: 20, 29 | week2: 50, 30 | week3: 100, 31 | week4: 30, 32 | }, 33 | { 34 | name: 'May', 35 | week1: 20, 36 | week2: 50, 37 | week3: 100, 38 | week4: 30, 39 | }, 40 | { 41 | name: 'Jun', 42 | week1: 20, 43 | week2: 50, 44 | week3: 100, 45 | week4: 30, 46 | }, 47 | { 48 | name: 'Jul', 49 | week1: 20, 50 | week2: 50, 51 | week3: 100, 52 | week4: 30, 53 | }, 54 | { 55 | name: 'Aug', 56 | week1: 20, 57 | week2: 50, 58 | week3: 100, 59 | week4: 30, 60 | }, 61 | { 62 | name: 'Sep', 63 | week1: 20, 64 | week2: 40, 65 | week3: 100, 66 | week4: 30, 67 | }, 68 | { 69 | name: 'Oct', 70 | week1: 20, 71 | week2: 50, 72 | week3: 100, 73 | week4: 30, 74 | }, 75 | { 76 | name: 'Nov', 77 | week1: 20, 78 | week2: 50, 79 | week3: 70, 80 | week4: 30, 81 | }, 82 | { 83 | name: 'Dec', 84 | week1: 20, 85 | week2: 50, 86 | week3: 100, 87 | week4: 30, 88 | }, 89 | ]; 90 | return ( 91 | <> 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | ); 107 | }; 108 | 109 | export default InstallMentGraph; 110 | -------------------------------------------------------------------------------- /frontend/pages/member/depositeChart.tsx: -------------------------------------------------------------------------------- 1 | import { Bar, BarChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; 2 | 3 | const DepositChart = (props) => { 4 | const data = [ 5 | { 6 | name: 'Jan', 7 | week1: 20, 8 | week2: 50, 9 | week3: 100, 10 | week4: 30, 11 | }, 12 | { 13 | name: 'Feb', 14 | week1: 20, 15 | week2: 80, 16 | week3: 100, 17 | week4: 30, 18 | }, 19 | { 20 | name: 'Mar', 21 | week1: 20, 22 | week2: 50, 23 | week3: 100, 24 | week4: 30, 25 | }, 26 | { 27 | name: 'Apr', 28 | week1: 20, 29 | week2: 50, 30 | week3: 100, 31 | week4: 30, 32 | }, 33 | { 34 | name: 'May', 35 | week1: 20, 36 | week2: 50, 37 | week3: 100, 38 | week4: 30, 39 | }, 40 | { 41 | name: 'Jun', 42 | week1: 20, 43 | week2: 50, 44 | week3: 100, 45 | week4: 30, 46 | }, 47 | { 48 | name: 'Jul', 49 | week1: 20, 50 | week2: 50, 51 | week3: 100, 52 | week4: 30, 53 | }, 54 | { 55 | name: 'Aug', 56 | week1: 20, 57 | week2: 50, 58 | week3: 100, 59 | week4: 30, 60 | }, 61 | { 62 | name: 'Sep', 63 | week1: 20, 64 | week2: 40, 65 | week3: 100, 66 | week4: 30, 67 | }, 68 | { 69 | name: 'Oct', 70 | week1: 20, 71 | week2: 50, 72 | week3: 100, 73 | week4: 30, 74 | }, 75 | { 76 | name: 'Nov', 77 | week1: 20, 78 | week2: 50, 79 | week3: 70, 80 | week4: 30, 81 | }, 82 | { 83 | name: 'Dec', 84 | week1: 20, 85 | week2: 50, 86 | week3: 100, 87 | week4: 30, 88 | }, 89 | ]; 90 | return ( 91 | <> 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | ); 107 | }; 108 | 109 | export default DepositChart; 110 | -------------------------------------------------------------------------------- /backend/templates/transaction/installment_posting.html: -------------------------------------------------------------------------------- 1 | {% extends 'base/base.html' %} 2 | 3 | {% block extrahead %} 4 | 5 | 6 | {% endblock %} 7 | 8 | 9 | {% block content %} 10 | 11 |
12 |
13 | কর্জ ফেরত 14 |
15 |
16 |
17 |
18 |
19 | {{ member_choice_form }} 20 | 21 |
22 |
23 |
24 | Next 25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 | {% if my_loan %} 37 |
38 | {% csrf_token %} 39 | {{ installment_form.as_p }} 40 | 41 |
42 | {% else %} 43 |

কোন কর্জ নেই

44 | {% endif %} 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | {% if my_loan %} 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | {% endif %} 75 | 76 |
সদস্যের নাম{{ member.name }}
অভিভাবকের নাম{{ member.guardian_name }}
দলের নাম{{ member.team }}
সিরিয়াল নম্বর{{ member.serial_number }}
কর্জের পরিমান{{ my_loan.amount }}
কর্জের স্থিতি{{ my_loan.total_due }}
77 |
78 |
79 | 80 |
81 |
82 | 83 |
84 | 85 | 86 |
87 | 88 | {% endblock %} 89 | 90 | {% block script %} 91 | 92 | 99 | {% endblock %} -------------------------------------------------------------------------------- /frontend/src/modules/dashboard/components/Chart.tsx: -------------------------------------------------------------------------------- 1 | import { Area, CartesianGrid, ComposedChart, ResponsiveContainer, Tooltip, XAxis } from 'recharts'; 2 | 3 | const data = [ 4 | { 5 | name: 'Jan', 6 | uv: 3000, 7 | pv: 3700, 8 | amt: 2400, 9 | }, 10 | { 11 | name: 'Feb', 12 | uv: 3000, 13 | pv: 3398, 14 | amt: 2210, 15 | }, 16 | { 17 | name: 'Mar', 18 | uv: 2000, 19 | pv: 3800, 20 | amt: 2290, 21 | }, 22 | { 23 | name: 'Apr', 24 | uv: 2780, 25 | pv: 3208, 26 | amt: 2000, 27 | }, 28 | { 29 | name: 'May', 30 | uv: 1890, 31 | pv: 3000, 32 | amt: 2181, 33 | }, 34 | { 35 | name: 'Jun', 36 | uv: 2390, 37 | pv: 3800, 38 | amt: 2500, 39 | }, 40 | { 41 | name: 'Aug', 42 | uv: 3490, 43 | pv: 4300, 44 | amt: 2100, 45 | }, 46 | { 47 | name: 'Sep', 48 | uv: 2490, 49 | pv: 2900, 50 | amt: 2100, 51 | }, 52 | { 53 | name: 'Oct', 54 | uv: 3490, 55 | pv: 4300, 56 | amt: 2100, 57 | }, 58 | { 59 | name: 'Nov', 60 | uv: 3490, 61 | pv: 4300, 62 | amt: 2100, 63 | }, 64 | { 65 | name: 'Dec', 66 | uv: 1490, 67 | pv: 2900, 68 | amt: 2100, 69 | }, 70 | ]; 71 | const Chart = () => { 72 | return ( 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 92 | 93 | 94 | 95 | 103 | 111 | 112 | 113 | ); 114 | }; 115 | 116 | export default Chart; 117 | --------------------------------------------------------------------------------