42 |
43 | {% include 'base/sidebar.html' %}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {% if messages %}
53 |
54 | {% for message in messages %}
55 |
56 | {{ message }}
57 |
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