7 Commits

Author SHA1 Message Date
VivianDee 3f59ed7da3 [add]: Oracle database configuration 2025-07-07 13:27:16 +01:00
ameye 0957c2ea37 Merge branch 'task_3' of DigiFi/FirstCore into master 2025-07-05 20:05:34 +00:00
Chinenye Nmoh 2d6ff1adc2 updated loan and repayment endpoint 2025-07-05 20:49:57 +01:00
ameye 6bed4d2dfa Merge branch 'task_3' of DigiFi/FirstCore into master 2025-05-28 12:40:51 +00:00
Chinenye Nmoh bbb919d933 completed the task 2025-05-28 13:14:09 +01:00
CHIEFSOFT\ameye 2c46a4390c Sample env 2025-05-26 06:54:20 -04:00
ameye fac6a80284 Merge branch 'testing' of DigiFi/FirstCore into master 2025-05-19 15:04:49 +00:00
14 changed files with 187 additions and 61 deletions
+32 -17
View File
@@ -1,26 +1,41 @@
VALID_APP_ID=********** SIMBRELLA_BASE_URL=***************
VALID_API_KEY=*************
BASIC_AUTH_USERNAME=******
BASIC_AUTH_PASSWORD=******
# Environment Variables
BASIC_AUTH_USERNAME=user
BASIC_AUTH_PASSWORD=password
SWAGGER_URL="/documentation" SWAGGER_URL="/documentation"
API_URL="/swagger.json" API_URL="/swagger.json"
JWT_SECRET_KEY=******
JWT_ACCESS_TOKEN_EXPIRES=******
JWT_REFRESH_TOKEN_EXPIRES=******
DATABASE_USER=*****
DATABASE_PASSWORD=*****
DATABASE_HOST=******
DATABASE_PORT=******
DATABASE_NAME=*****
# Flask Configuration # Flask Configuration
FLASK_APP=wsgi.py FLASK_APP=wsgi.py
FLASK_ENV=development FLASK_ENV=development
APP_PORT=4500 APP_PORT=4700
#Database Configuration
#DATABASE_USER=firstadvance
#DATABASE_PASSWORD=FirstAdvance!
#DATABASE_HOST=10.20.30.60
#DATABASE_PORT=5432
#DATABASE_NAME=firstadvancedev
DATABASE_USER=system
DATABASE_PASSWORD=FIRSTADV_PASS
DATABASE_HOST=10.10.33.65
DATABASE_PORT=1521
DATABASE_SID=FREE
# DATABASE_USER=firstadvance
# DATABASE_PASSWORD=FirstAdvance!
# DATABASE_HOST=dev-data.simbrellang.net
# DATABASE_PORT=10532
# DATABASE_NAME=firstadvancedev
#Events if Needed
KAFKA_TIMEOUT=45000.0
KAFKA_BROKER="10.20.30.50:9092"
SIMBRELLA_BASE_URL=***************
+6 -3
View File
@@ -18,7 +18,10 @@ ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0 ENV FLASK_RUN_HOST=0.0.0.0
#COPY scripts/enterypointone.sh scripts/enterypointone.sh #COPY scripts/enterypointone.sh scripts/enterypointone.sh
#RUN chmod +x scripts/entry.sh
#ENTRYPOINT ["scripts/entry.sh"]
RUN chmod +x scripts/enterypointone.sh # Run the application
CMD [ "sh", "-c", \
ENTRYPOINT ["scripts/enterypointone.sh"] "echo 'Starting Gunicorn server...' && \
exec gunicorn -w 4 -b 0.0.0.0:5000 wsgi:wsgi_app"]
+2 -1
View File
@@ -123,7 +123,8 @@ def get_loans():
'page': request.args.get('page', 1), 'page': request.args.get('page', 1),
'limit': request.args.get('limit', 20) 'limit': request.args.get('limit', 20)
} }
# logger.info(f"Get loans request received with filters: {filters}") #logger.info(f"Get loans request received with filters: {filters}")
response = LoanService.process_request(filters) response = LoanService.process_request(filters)
return response return response
+30 -34
View File
@@ -4,34 +4,30 @@ from app.api.services.base_service import BaseService
from app.models.transaction import Transaction from app.models.transaction import Transaction
from app.models.loan import Loan from app.models.loan import Loan
from sqlalchemy import func, desc from sqlalchemy import func, desc
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from app.extensions import db from app.extensions import db
from app.api.enums.transaction_type import TransactionType
class DashboardService(BaseService): class DashboardService(BaseService):
@staticmethod @staticmethod
def get_dashboard_data(): def get_dashboard_data():
"""
Get dashboard summary data.
Returns:
dict: A standardized response with dashboard data.
"""
try: try:
# Get current date and start of the week
now = datetime.now() now = datetime.now()
start_of_week = now - timedelta(days=now.weekday()) start_of_week = now - timedelta(days=now.weekday())
start_of_week = start_of_week.replace(hour=0, minute=0, second=0, microsecond=0) start_of_week = start_of_week.replace(hour=0, minute=0, second=0, microsecond=0)
# Get loans data for the current week # Calculate 24 hours ago
last_24_hours = datetime.now(timezone.utc) - timedelta(hours=24)
# Loans this week
loans_this_week = db.session.query( loans_this_week = db.session.query(
func.sum(Loan.initial_loan_amount) func.sum(Loan.initial_loan_amount)
).filter( ).filter(
Loan.created_at >= start_of_week Loan.created_at >= start_of_week
).scalar() or 0 ).scalar() or 0
# Get payments data for the current week # Payments this week
# Assuming payments are transactions with type 'PAYMENT'
payments_this_week = db.session.query( payments_this_week = db.session.query(
func.count(Transaction.id) func.count(Transaction.id)
).filter( ).filter(
@@ -39,51 +35,53 @@ class DashboardService(BaseService):
Transaction.type == 'PAYMENT' Transaction.type == 'PAYMENT'
).scalar() or 0 ).scalar() or 0
# Get request summary counts
# These are placeholders - needed to adjust based on your actual data model # Request summary for the last 24 hours
eligibility_check_count = db.session.query( eligibility_check_count = db.session.query(
func.count(Transaction.id) func.count(Transaction.id)
).filter( ).filter(
Transaction.type == 'ELIGIBILITY_CHECK' Transaction.type == TransactionType.ELIGIBILITY_CHECK.value,
Transaction.created_at >= last_24_hours
).scalar() or 0 ).scalar() or 0
select_offer_count = db.session.query( select_offer_count = db.session.query(
func.count(Transaction.id) func.count(Transaction.id)
).filter( ).filter(
Transaction.type == 'SELECT_OFFER' Transaction.type == TransactionType.SELECT_OFFER.value,
Transaction.created_at >= last_24_hours
).scalar() or 0 ).scalar() or 0
provide_loan_count = db.session.query( provide_loan_count = db.session.query(
func.count(Transaction.id) func.count(Transaction.id)
).filter( ).filter(
Transaction.type == 'PROVIDE_LOAN' Transaction.type == TransactionType.PROVIDE_LOAN.value,
Transaction.created_at >= last_24_hours
).scalar() or 0 ).scalar() or 0
repayment_count = db.session.query( repayment_count = db.session.query(
func.count(Transaction.id) func.count(Transaction.id)
).filter( ).filter(
Transaction.type == 'REPAYMENT' Transaction.type == TransactionType.REPAYMENT.value,
Transaction.created_at >= last_24_hours
).scalar() or 0 ).scalar() or 0
# Get recent transactions # Recent transactions (not limited to 24 hrs, just latest 15)
recent_transactions = Transaction.query.order_by( recent_transactions = Transaction.query.order_by(
Transaction.id.desc() Transaction.id.desc()
).limit(15).all() ).limit(15).all()
# Format recent transactions recent_transactions_data = [{
recent_transactions_data = [] 'id': t.id,
for transaction in recent_transactions: 'transaction_id': t.transaction_id,
recent_transactions_data.append({ 'account_id': t.account_id,
'id': transaction.id, 'type': t.type,
'transaction_id': transaction.transaction_id, 'channel': t.channel,
'account_id': transaction.account_id, 'created_at': t.created_at.isoformat() if t.created_at else None,
'type': transaction.type, 'updated_at': t.updated_at.isoformat() if t.updated_at else None
'channel': transaction.channel, } for t in recent_transactions]
'created_at': transaction.created_at.isoformat() if transaction.created_at else None,
'updated_at': transaction.updated_at.isoformat() if transaction.updated_at else None
})
# Prepare response data # Final response
dashboard_data = { dashboard_data = {
"loans": { "loans": {
"value": float(loans_this_week), "value": float(loans_this_week),
@@ -110,6 +108,4 @@ class DashboardService(BaseService):
except Exception as e: except Exception as e:
logger.error(f"An error occurred while getting dashboard data: {str(e)}", exc_info=True) logger.error(f"An error occurred while getting dashboard data: {str(e)}", exc_info=True)
return jsonify({ return jsonify({"message": "Internal Server Error"}), 500
"message": "Internal Server Error"
}), 500
+10 -2
View File
@@ -81,7 +81,7 @@ class LoanService:
limit=limit limit=limit
) )
logger.info(f"Result from loans model cme back") logger.info(f"Result from loans model cme back ")
# Convert loans to dictionary format # Convert loans to dictionary format
loans_data = [] loans_data = []
@@ -98,6 +98,8 @@ class LoanService:
'current_loan_amount': loan.current_loan_amount, 'current_loan_amount': loan.current_loan_amount,
'status': loan.status, 'status': loan.status,
'tenor': loan.tenor, 'tenor': loan.tenor,
'balance': loan.balance,
'reference': loan.reference,
'product_id': loan.product_id, 'product_id': loan.product_id,
'default_penalty_fee': loan.default_penalty_fee, 'default_penalty_fee': loan.default_penalty_fee,
'continuous_fee': loan.continuous_fee, 'continuous_fee': loan.continuous_fee,
@@ -106,7 +108,13 @@ class LoanService:
'installment_amount': loan.installment_amount, 'installment_amount': loan.installment_amount,
'due_date': loan.due_date.isoformat() if loan.due_date else None, 'due_date': loan.due_date.isoformat() if loan.due_date else None,
'created_at': loan.created_at.isoformat() if loan.created_at else None, 'created_at': loan.created_at.isoformat() if loan.created_at else None,
'updated_at': loan.updated_at.isoformat() if loan.updated_at else None 'updated_at': loan.updated_at.isoformat() if loan.updated_at else None,
'disburseResult': loan.disburse_result,
'disburseDescription': loan.disburse_description,
'verifyResult': loan.verify_result,
'verifyDescription': loan.verify_description,
'disburseDate': loan.disburse_date.isoformat() if loan.disburse_date else None,
'disburseVerify': loan.disburse_verify.isoformat() if loan.disburse_verify else None,
}) })
# Calculate total pages # Calculate total pages
+9 -1
View File
@@ -69,7 +69,15 @@ class RepaymentService:
'product_id': repayment.product_id, 'product_id': repayment.product_id,
'transaction_id': repayment.transaction_id, 'transaction_id': repayment.transaction_id,
'created_at': repayment.created_at.isoformat() if repayment.created_at else None, 'created_at': repayment.created_at.isoformat() if repayment.created_at else None,
'updated_at': repayment.updated_at.isoformat() if repayment.updated_at else None 'updated_at': repayment.updated_at.isoformat() if repayment.updated_at else None,
'repay_date': repayment.repay_date.isoformat() if repayment.repay_date else None,
'initiated_by': repayment.initiated_by,
'salary_amount': repayment.salary_amount,
'verify_date': repayment.verify_date.isoformat() if repayment.verify_date else None,
'repay_result': repayment.repay_result,
'repay_description': repayment.repay_description,
'verify_result': repayment.verify_result,
'verify_description': repayment.verify_description
}) })
# Calculate total pages # Calculate total pages
+1
View File
@@ -72,6 +72,7 @@ class TransactionService:
'transaction_id': transaction.transaction_id, 'transaction_id': transaction.transaction_id,
'account_id': transaction.account_id, 'account_id': transaction.account_id,
'type': transaction.type, 'type': transaction.type,
'customer_id': transaction.customer_id,
'channel': transaction.channel, 'channel': transaction.channel,
'created_at': transaction.created_at.isoformat(), 'created_at': transaction.created_at.isoformat(),
'updated_at': transaction.updated_at.isoformat() 'updated_at': transaction.updated_at.isoformat()
+6 -1
View File
@@ -19,8 +19,13 @@ class Config:
DATABASE_HOST = os.environ.get("DATABASE_HOST") DATABASE_HOST = os.environ.get("DATABASE_HOST")
DATABASE_PORT = os.environ.get("DATABASE_PORT", 10532) DATABASE_PORT = os.environ.get("DATABASE_PORT", 10532)
DATABASE_NAME = os.environ.get("DATABASE_NAME") DATABASE_NAME = os.environ.get("DATABASE_NAME")
DATABASE_SID = os.environ.get("DATABASE_SID", "FREE")
DNS = f"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={DATABASE_HOST})(PORT={DATABASE_PORT}))(CONNECT_DATA=(SID={DATABASE_SID})))"
# SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}"
SQLALCHEMY_DATABASE_URI = (f"oracle+oracledb://{DATABASE_USER}:{DATABASE_PASSWORD}@{DNS}")
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}"
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
SIMBRELLA_BASE_URL = os.getenv("SIMBRELLA_BASE_URL", "http://127.0.0.1:6337") SIMBRELLA_BASE_URL = os.getenv("SIMBRELLA_BASE_URL", "http://127.0.0.1:6337")
+17 -1
View File
@@ -36,6 +36,14 @@ class Loan(db.Model):
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc)) created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc))
updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc)) updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
eligible_amount = db.Column(db.Float, nullable=True, default=0.0) eligible_amount = db.Column(db.Float, nullable=True, default=0.0)
disburse_date = db.Column(db.DateTime, nullable=True)
disburse_verify = db.Column(db.DateTime, nullable=True)
disburse_result = db.Column(db.String(10), nullable=True)
disburse_description = db.Column(db.String(100), nullable=True)
verify_result = db.Column(db.String(10), nullable=True)
verify_description = db.Column(db.String(100), nullable=True)
reference = db.Column(db.String(50), nullable=True)
balance = db.Column(db.Float, nullable=True, default=0.0)
customer = relationship( customer = relationship(
"Customer", "Customer",
@@ -147,7 +155,15 @@ class Loan(db.Model):
'installment_amount': self.installment_amount, 'installment_amount': self.installment_amount,
'due_date': self.due_date.isoformat() if self.due_date else None, 'due_date': self.due_date.isoformat() if self.due_date else None,
'created_at': self.created_at.isoformat() if self.created_at else None, 'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None 'updated_at': self.updated_at.isoformat() if self.updated_at else None,
'disburseResult': self.disburse_result,
'disburseDescription': self.disburse_description,
'verifyResult': self.verify_result,
'verifyDescription': self.verify_description,
'disburseDate': self.disburse_date.isoformat() if self.disburse_date else None,
'disburseVerify': self.disburse_verify.isoformat() if self.disburse_verify else None,
'reference': self.reference,
'balance': self.balance,
} }
def __repr__(self): def __repr__(self):
+18 -1
View File
@@ -12,6 +12,14 @@ class Repayment(db.Model):
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc)) created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc))
updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc)) updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
transaction_id = db.Column(db.String(50)) transaction_id = db.Column(db.String(50))
repay_date = db.Column(db.DateTime, nullable=True)
initiated_by = db.Column(db.String(50), nullable=True)
salary_amount = db.Column(db.Float, nullable=True, default=0.0)
verify_date = db.Column(db.DateTime, nullable=True)
repay_result = db.Column(db.String(10), nullable=True)
repay_description = db.Column(db.String(100), nullable=True)
verify_result = db.Column(db.String(10), nullable=True)
verify_description = db.Column(db.String(100), nullable=True)
@classmethod @classmethod
def get_all_repayments(cls, loan_id=None, customer_id=None, product_id=None, def get_all_repayments(cls, loan_id=None, customer_id=None, product_id=None,
@@ -71,7 +79,16 @@ class Repayment(db.Model):
'product_id': self.product_id, 'product_id': self.product_id,
'transaction_id': self.transaction_id, 'transaction_id': self.transaction_id,
'created_at': self.created_at.isoformat() if self.created_at else None, 'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None 'updated_at': self.updated_at.isoformat() if self.updated_at else None,
'repay_date': self.repay_date.isoformat() if self.repay_date else None,
'initiated_by': self.initiated_by,
'salary_amount': self.salary_amount,
'verify_date': self.verify_date.isoformat() if self.verify_date else None,
'repay_result': self.repay_result,
'repay_description': self.repay_description,
'verify_result': self.verify_result,
'verify_description': self.verify_description
} }
def __repr__(self): def __repr__(self):
+11
View File
@@ -76,6 +76,17 @@
"example": 10500.0, "example": 10500.0,
"nullable": true "nullable": true
}, },
"balance": {
"type": "number",
"format": "float",
"example": 5000.0,
"nullable": true
},
"reference": {
"type": "string",
"example": "REF12345",
"nullable": true
},
"installment_amount": { "installment_amount": {
"type": "number", "type": "number",
"format": "float", "format": "float",
@@ -23,6 +23,50 @@
"example": "TRX123456", "example": "TRX123456",
"nullable": true "nullable": true
}, },
"initiated_by": {
"type": "string",
"example": "system",
"nullable": true
},
"salary_amount": {
"type": "number",
"format": "float",
"example": 1000.0,
"nullable": true
},
"repay_date": {
"type": "string",
"format": "date-time",
"example": "2025-04-10T16:45:47.879552Z",
"nullable": true
},
"verify_date": {
"type": "string",
"format": "date-time",
"example": "2025-04-10T16:45:47.879552Z",
"nullable": true
},
"repay_result": {
"type": "string",
"example": "success",
"nullable": true
},
"repay_description": {
"type": "string",
"example": "Repayment processed successfully",
"nullable": true
},
"verify_result": {
"type": "string",
"example": "verified",
"nullable": true
},
"verify_description": {
"type": "string",
"example": "Verification completed successfully",
"nullable": true
},
"created_at": { "created_at": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
+1
View File
@@ -6,6 +6,7 @@ flask-sqlalchemy
flask-migrate flask-migrate
psycopg2-binary psycopg2-binary
alembic alembic
oracledb
# Schema for validations # Schema for validations
Flask-Marshmallow==0.15.0 Flask-Marshmallow==0.15.0