mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
1167 lines
37 KiB
Go
1167 lines
37 KiB
Go
package controllers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
|
|
"fotbal-club/internal/models"
|
|
)
|
|
|
|
type FinancialController struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewFinancialController(db *gorm.DB) *FinancialController {
|
|
return &FinancialController{db: db}
|
|
}
|
|
|
|
// Budget Management
|
|
|
|
// GetBudgets retrieves all budgets with optional filtering
|
|
func (fc *FinancialController) GetBudgets(c *gin.Context) {
|
|
var budgets []models.Budget
|
|
query := fc.db.Preload("Expenses")
|
|
|
|
// Filter by fiscal year if provided
|
|
if fiscalYear := c.Query("fiscal_year"); fiscalYear != "" {
|
|
if year, err := strconv.Atoi(fiscalYear); err == nil {
|
|
query = query.Where("fiscal_year = ?", year)
|
|
}
|
|
}
|
|
|
|
// Filter by category if provided
|
|
if category := c.Query("category"); category != "" {
|
|
query = query.Where("category = ?", category)
|
|
}
|
|
|
|
// Filter by active status if provided
|
|
if active := c.Query("active"); active != "" {
|
|
if isActive, err := strconv.ParseBool(active); err == nil {
|
|
query = query.Where("active = ?", isActive)
|
|
}
|
|
}
|
|
|
|
if err := query.Order("category, name").Find(&budgets).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve budgets"})
|
|
return
|
|
}
|
|
|
|
// Calculate budget statistics for each budget
|
|
for i := range budgets {
|
|
budgets[i].CurrentSpend = fc.calculateBudgetSpend(budgets[i].ID)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, budgets)
|
|
}
|
|
|
|
// GetBudget retrieves a single budget by ID
|
|
func (fc *FinancialController) GetBudget(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var budget models.Budget
|
|
|
|
if err := fc.db.Preload("Expenses").First(&budget, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Budget not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve budget"})
|
|
return
|
|
}
|
|
|
|
// Calculate current spend
|
|
budget.CurrentSpend = fc.calculateBudgetSpend(budget.ID)
|
|
|
|
c.JSON(http.StatusOK, budget)
|
|
}
|
|
|
|
// CreateBudget creates a new budget
|
|
func (fc *FinancialController) CreateBudget(c *gin.Context) {
|
|
var budget models.Budget
|
|
if err := c.ShouldBindJSON(&budget); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Set default values
|
|
if budget.FiscalYear == 0 {
|
|
budget.FiscalYear = time.Now().Year()
|
|
}
|
|
if budget.StartDate.IsZero() {
|
|
budget.StartDate = time.Date(budget.FiscalYear, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
}
|
|
if budget.EndDate.IsZero() {
|
|
budget.EndDate = time.Date(budget.FiscalYear, 12, 31, 23, 59, 59, 0, time.UTC)
|
|
}
|
|
|
|
// Get current user ID from context (set by auth middleware)
|
|
userID, exists := c.Get("user_id")
|
|
if exists {
|
|
budget.CreatedBy = userID.(uint)
|
|
}
|
|
|
|
if err := fc.db.Create(&budget).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create budget"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, budget)
|
|
}
|
|
|
|
// UpdateBudget updates an existing budget
|
|
func (fc *FinancialController) UpdateBudget(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var budget models.Budget
|
|
|
|
if err := fc.db.First(&budget, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Budget not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve budget"})
|
|
return
|
|
}
|
|
|
|
var updateData models.Budget
|
|
if err := c.ShouldBindJSON(&updateData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Update fields
|
|
budget.Name = updateData.Name
|
|
budget.Description = updateData.Description
|
|
budget.Category = updateData.Category
|
|
budget.YearlyLimit = updateData.YearlyLimit
|
|
budget.MonthlyLimit = updateData.MonthlyLimit
|
|
budget.FiscalYear = updateData.FiscalYear
|
|
budget.StartDate = updateData.StartDate
|
|
budget.EndDate = updateData.EndDate
|
|
budget.Active = updateData.Active
|
|
budget.AlertThreshold = updateData.AlertThreshold
|
|
|
|
// Get current user ID from context
|
|
userID, exists := c.Get("user_id")
|
|
if exists {
|
|
budget.UpdatedBy = userID.(uint)
|
|
}
|
|
|
|
if err := fc.db.Save(&budget).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update budget"})
|
|
return
|
|
}
|
|
|
|
// Recalculate current spend
|
|
budget.CurrentSpend = fc.calculateBudgetSpend(budget.ID)
|
|
|
|
c.JSON(http.StatusOK, budget)
|
|
}
|
|
|
|
// DeleteBudget deletes a budget
|
|
func (fc *FinancialController) DeleteBudget(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
// Check if budget has expenses
|
|
var expenseCount int64
|
|
fc.db.Model(&models.Expense{}).Where("budget_id = ?", id).Count(&expenseCount)
|
|
if expenseCount > 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot delete budget with associated expenses"})
|
|
return
|
|
}
|
|
|
|
if err := fc.db.Delete(&models.Budget{}, id).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete budget"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Budget deleted successfully"})
|
|
}
|
|
|
|
// GetBudgetCategories retrieves all unique budget categories
|
|
func (fc *FinancialController) GetBudgetCategories(c *gin.Context) {
|
|
var categories []string
|
|
if err := fc.db.Model(&models.Budget{}).Distinct("category").Pluck("category", &categories).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve categories"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, categories)
|
|
}
|
|
|
|
// GetBudgetOverview provides budget summary and statistics
|
|
func (fc *FinancialController) GetBudgetOverview(c *gin.Context) {
|
|
fiscalYear := c.Query("fiscal_year")
|
|
if fiscalYear == "" {
|
|
fiscalYear = strconv.Itoa(time.Now().Year())
|
|
}
|
|
|
|
year, err := strconv.Atoi(fiscalYear)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid fiscal year"})
|
|
return
|
|
}
|
|
|
|
var overview struct {
|
|
TotalBudget float64 `json:"total_budget"`
|
|
TotalSpend float64 `json:"total_spend"`
|
|
RemainingBudget float64 `json:"remaining_budget"`
|
|
BudgetCount int `json:"budget_count"`
|
|
OverBudgetCount int `json:"over_budget_count"`
|
|
AlertBudgetCount int `json:"alert_budget_count"`
|
|
Categories []CategorySummary `json:"categories"`
|
|
}
|
|
|
|
// Get all budgets for the fiscal year
|
|
var budgets []models.Budget
|
|
if err := fc.db.Where("fiscal_year = ? AND active = ?", year, true).Find(&budgets).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve budgets"})
|
|
return
|
|
}
|
|
|
|
// Calculate totals and category summaries
|
|
categoryMap := make(map[string]*CategorySummary)
|
|
for _, budget := range budgets {
|
|
currentSpend := fc.calculateBudgetSpend(budget.ID)
|
|
|
|
overview.TotalBudget += budget.YearlyLimit
|
|
overview.TotalSpend += currentSpend
|
|
|
|
if currentSpend > budget.YearlyLimit {
|
|
overview.OverBudgetCount++
|
|
} else if currentSpend > (budget.YearlyLimit * budget.AlertThreshold / 100) {
|
|
overview.AlertBudgetCount++
|
|
}
|
|
|
|
// Category summary
|
|
if _, exists := categoryMap[budget.Category]; !exists {
|
|
categoryMap[budget.Category] = &CategorySummary{
|
|
Category: budget.Category,
|
|
BudgetTotal: 0,
|
|
SpendTotal: 0,
|
|
BudgetCount: 0,
|
|
}
|
|
}
|
|
cat := categoryMap[budget.Category]
|
|
cat.BudgetTotal += budget.YearlyLimit
|
|
cat.SpendTotal += currentSpend
|
|
cat.BudgetCount++
|
|
}
|
|
|
|
overview.RemainingBudget = overview.TotalBudget - overview.TotalSpend
|
|
overview.BudgetCount = len(budgets)
|
|
|
|
// Convert category map to slice
|
|
for _, cat := range categoryMap {
|
|
overview.Categories = append(overview.Categories, *cat)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, overview)
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func (fc *FinancialController) calculateBudgetSpend(budgetID uint) float64 {
|
|
var totalSpend float64
|
|
fc.db.Model(&models.Expense{}).Where("budget_id = ? AND status IN ?", budgetID, []string{"approved", "reimbursed"}).Select("COALESCE(SUM(total_amount), 0)").Scan(&totalSpend)
|
|
return totalSpend
|
|
}
|
|
|
|
// CategorySummary represents budget summary by category
|
|
type CategorySummary struct {
|
|
Category string `json:"category"`
|
|
BudgetTotal float64 `json:"budget_total"`
|
|
SpendTotal float64 `json:"spend_total"`
|
|
BudgetCount int `json:"budget_count"`
|
|
}
|
|
|
|
// Sponsorship Management
|
|
|
|
// GetSponsorships retrieves all sponsorships with filtering
|
|
func (fc *FinancialController) GetSponsorships(c *gin.Context) {
|
|
var sponsorships []models.Sponsorship
|
|
query := fc.db.Preload("Payments").Preload("Documents")
|
|
|
|
// Filter by status if provided
|
|
if status := c.Query("status"); status != "" {
|
|
query = query.Where("status = ?", status)
|
|
}
|
|
|
|
// Filter by contract type if provided
|
|
if contractType := c.Query("contract_type"); contractType != "" {
|
|
query = query.Where("contract_type = ?", contractType)
|
|
}
|
|
|
|
// Filter by active sponsorships
|
|
if active := c.Query("active"); active == "true" {
|
|
query = query.Where("status = ? AND end_date >= ?", "active", time.Now())
|
|
}
|
|
|
|
if err := query.Order("sponsor_name").Find(&sponsorships).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve sponsorships"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, sponsorships)
|
|
}
|
|
|
|
// GetSponsorship retrieves a single sponsorship by ID
|
|
func (fc *FinancialController) GetSponsorship(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var sponsorship models.Sponsorship
|
|
|
|
if err := fc.db.Preload("Payments").Preload("Documents").First(&sponsorship, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Sponsorship not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve sponsorship"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, sponsorship)
|
|
}
|
|
|
|
// CreateSponsorship creates a new sponsorship
|
|
func (fc *FinancialController) CreateSponsorship(c *gin.Context) {
|
|
var sponsorship models.Sponsorship
|
|
if err := c.ShouldBindJSON(&sponsorship); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Set default values
|
|
if sponsorship.Status == "" {
|
|
sponsorship.Status = "active"
|
|
}
|
|
if sponsorship.Currency == "" {
|
|
sponsorship.Currency = "CZK"
|
|
}
|
|
|
|
// Get current user ID from context
|
|
userID, exists := c.Get("user_id")
|
|
if exists {
|
|
sponsorship.CreatedBy = userID.(uint)
|
|
}
|
|
|
|
if err := fc.db.Create(&sponsorship).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create sponsorship"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, sponsorship)
|
|
}
|
|
|
|
// UpdateSponsorship updates an existing sponsorship
|
|
func (fc *FinancialController) UpdateSponsorship(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var sponsorship models.Sponsorship
|
|
|
|
if err := fc.db.First(&sponsorship, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Sponsorship not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve sponsorship"})
|
|
return
|
|
}
|
|
|
|
var updateData models.Sponsorship
|
|
if err := c.ShouldBindJSON(&updateData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Update fields
|
|
sponsorship.SponsorName = updateData.SponsorName
|
|
sponsorship.SponsorLogo = updateData.SponsorLogo
|
|
sponsorship.ContactPerson = updateData.ContactPerson
|
|
sponsorship.ContactEmail = updateData.ContactEmail
|
|
sponsorship.ContactPhone = updateData.ContactPhone
|
|
sponsorship.ContractNumber = updateData.ContractNumber
|
|
sponsorship.ContractType = updateData.ContractType
|
|
sponsorship.TotalValue = updateData.TotalValue
|
|
sponsorship.PaymentSchedule = updateData.PaymentSchedule
|
|
sponsorship.Currency = updateData.Currency
|
|
sponsorship.StartDate = updateData.StartDate
|
|
sponsorship.EndDate = updateData.EndDate
|
|
sponsorship.AutoRenewal = updateData.AutoRenewal
|
|
sponsorship.RenewalNotice = updateData.RenewalNotice
|
|
sponsorship.Benefits = updateData.Benefits
|
|
sponsorship.Obligations = updateData.Obligations
|
|
sponsorship.Status = updateData.Status
|
|
sponsorship.LastPaymentDate = updateData.LastPaymentDate
|
|
sponsorship.NextPaymentDate = updateData.NextPaymentDate
|
|
|
|
// Get current user ID from context
|
|
userID, exists := c.Get("user_id")
|
|
if exists {
|
|
sponsorship.UpdatedBy = userID.(uint)
|
|
}
|
|
|
|
if err := fc.db.Save(&sponsorship).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update sponsorship"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, sponsorship)
|
|
}
|
|
|
|
// DeleteSponsorship deletes a sponsorship
|
|
func (fc *FinancialController) DeleteSponsorship(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
if err := fc.db.Delete(&models.Sponsorship{}, id).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete sponsorship"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Sponsorship deleted successfully"})
|
|
}
|
|
|
|
// GetSponsorshipOverview provides sponsorship summary
|
|
func (fc *FinancialController) GetSponsorshipOverview(c *gin.Context) {
|
|
var overview struct {
|
|
TotalSponsors int64 `json:"total_sponsors"`
|
|
ActiveSponsors int64 `json:"active_sponsors"`
|
|
TotalValue float64 `json:"total_value"`
|
|
ReceivedThisYear float64 `json:"received_this_year"`
|
|
PendingThisYear float64 `json:"pending_this_year"`
|
|
ExpiringNextMonth int64 `json:"expiring_next_month"`
|
|
ContractTypes []ContractTypeSummary `json:"contract_types"`
|
|
}
|
|
|
|
currentYear := time.Now().Year()
|
|
nextMonth := time.Now().AddDate(0, 1, 0)
|
|
|
|
// Count sponsors
|
|
fc.db.Model(&models.Sponsorship{}).Count(&overview.TotalSponsors)
|
|
fc.db.Model(&models.Sponsorship{}).Where("status = ? AND end_date >= ?", "active", time.Now()).Count(&overview.ActiveSponsors)
|
|
|
|
// Calculate total value
|
|
fc.db.Model(&models.Sponsorship{}).Where("status = ?", "active").Select("COALESCE(SUM(total_value), 0)").Scan(&overview.TotalValue)
|
|
|
|
// Calculate payments this year
|
|
fc.db.Model(&models.SponsorshipPayment{}).
|
|
Where("EXTRACT(YEAR FROM payment_date) = ? AND status = ?", currentYear, "received").
|
|
Select("COALESCE(SUM(amount), 0)").Scan(&overview.ReceivedThisYear)
|
|
|
|
fc.db.Model(&models.SponsorshipPayment{}).
|
|
Where("EXTRACT(YEAR FROM payment_date) = ? AND status = ?", currentYear, "expected").
|
|
Select("COALESCE(SUM(amount), 0)").Scan(&overview.PendingThisYear)
|
|
|
|
// Count expiring sponsorships
|
|
fc.db.Model(&models.Sponsorship{}).
|
|
Where("status = ? AND end_date <= ? AND end_date >= ?", "active", nextMonth, time.Now()).
|
|
Count(&overview.ExpiringNextMonth)
|
|
|
|
// Contract type summary
|
|
var contractTypes []string
|
|
fc.db.Model(&models.Sponsorship{}).Distinct("contract_type").Pluck("contract_type", &contractTypes)
|
|
|
|
for _, contractType := range contractTypes {
|
|
var count int64
|
|
var totalValue float64
|
|
fc.db.Model(&models.Sponsorship{}).Where("contract_type = ? AND status = ?", contractType, "active").Count(&count)
|
|
fc.db.Model(&models.Sponsorship{}).Where("contract_type = ? AND status = ?", contractType, "active").Select("COALESCE(SUM(total_value), 0)").Scan(&totalValue)
|
|
|
|
overview.ContractTypes = append(overview.ContractTypes, ContractTypeSummary{
|
|
Type: contractType,
|
|
Count: int(count),
|
|
TotalValue: totalValue,
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, overview)
|
|
}
|
|
|
|
type ContractTypeSummary struct {
|
|
Type string `json:"type"`
|
|
Count int `json:"count"`
|
|
TotalValue float64 `json:"total_value"`
|
|
}
|
|
|
|
// Expense Management
|
|
|
|
// GetExpenses retrieves all expenses with filtering and pagination
|
|
func (fc *FinancialController) GetExpenses(c *gin.Context) {
|
|
var expenses []models.Expense
|
|
query := fc.db.Preload("Budget").Preload("Documents")
|
|
|
|
// Parse pagination parameters
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
|
|
offset := (page - 1) * limit
|
|
|
|
// Filter by status if provided
|
|
if status := c.Query("status"); status != "" {
|
|
query = query.Where("status = ?", status)
|
|
}
|
|
|
|
// Filter by category if provided
|
|
if category := c.Query("category"); category != "" {
|
|
query = query.Where("category = ?", category)
|
|
}
|
|
|
|
// Filter by budget if provided
|
|
if budgetID := c.Query("budget_id"); budgetID != "" {
|
|
query = query.Where("budget_id = ?", budgetID)
|
|
}
|
|
|
|
// Filter by date range if provided
|
|
if startDate := c.Query("start_date"); startDate != "" {
|
|
if date, err := time.Parse("2006-01-02", startDate); err == nil {
|
|
query = query.Where("expense_date >= ?", date)
|
|
}
|
|
}
|
|
if endDate := c.Query("end_date"); endDate != "" {
|
|
if date, err := time.Parse("2006-01-02", endDate); err == nil {
|
|
query = query.Where("expense_date <= ?", date)
|
|
}
|
|
}
|
|
|
|
// Get total count for pagination
|
|
var total int64
|
|
countQuery := query
|
|
if err := countQuery.Model(&models.Expense{}).Count(&total).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count expenses"})
|
|
return
|
|
}
|
|
|
|
// Apply pagination and ordering
|
|
if err := query.Order("expense_date DESC, created_at DESC").Limit(limit).Offset(offset).Find(&expenses).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve expenses"})
|
|
return
|
|
}
|
|
|
|
// Return paginated response
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"expenses": expenses,
|
|
"total": total,
|
|
"page": page,
|
|
"limit": limit,
|
|
"pages": (total + int64(limit) - 1) / int64(limit),
|
|
})
|
|
}
|
|
|
|
// GetExpense retrieves a single expense by ID
|
|
func (fc *FinancialController) GetExpense(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var expense models.Expense
|
|
|
|
if err := fc.db.Preload("Budget").Preload("Documents").First(&expense, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Expense not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve expense"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, expense)
|
|
}
|
|
|
|
// CreateExpense creates a new expense
|
|
func (fc *FinancialController) CreateExpense(c *gin.Context) {
|
|
var expense models.Expense
|
|
if err := c.ShouldBindJSON(&expense); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Set default values
|
|
if expense.Currency == "" {
|
|
expense.Currency = "CZK"
|
|
}
|
|
if expense.VATRate == 0 {
|
|
expense.VATRate = 21
|
|
}
|
|
if expense.Status == "" {
|
|
expense.Status = "pending"
|
|
}
|
|
if expense.ExpenseDate.IsZero() {
|
|
expense.ExpenseDate = time.Now()
|
|
}
|
|
|
|
// Calculate VAT and total amounts if not provided
|
|
if expense.VATAmount == 0 && expense.Amount > 0 {
|
|
expense.VATAmount = expense.Amount * expense.VATRate / 100
|
|
}
|
|
if expense.TotalAmount == 0 {
|
|
expense.TotalAmount = expense.Amount + expense.VATAmount
|
|
}
|
|
|
|
// Get current user ID from context
|
|
userID, exists := c.Get("user_id")
|
|
if exists {
|
|
expense.CreatedBy = userID.(uint)
|
|
}
|
|
|
|
// Auto-approve if amount is below threshold and approval is not required
|
|
// This would need to check FinancialSettings, but for now auto-approve amounts <= 1000
|
|
if expense.Amount <= 1000 {
|
|
expense.Status = "approved"
|
|
now := time.Now()
|
|
expense.ApprovedAt = &now
|
|
if userID != nil {
|
|
expense.ApprovedBy = userID.(uint)
|
|
}
|
|
}
|
|
|
|
if err := fc.db.Create(&expense).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create expense"})
|
|
return
|
|
}
|
|
|
|
// Reload with associations
|
|
if err := fc.db.Preload("Budget").Preload("Documents").First(&expense, expense.ID).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve created expense"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, expense)
|
|
}
|
|
|
|
// UpdateExpense updates an existing expense
|
|
func (fc *FinancialController) UpdateExpense(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var expense models.Expense
|
|
|
|
if err := fc.db.First(&expense, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Expense not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve expense"})
|
|
return
|
|
}
|
|
|
|
var updateData models.Expense
|
|
if err := c.ShouldBindJSON(&updateData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Don't allow updating approved/reimbursed expenses unless admin
|
|
if expense.Status == "approved" || expense.Status == "reimbursed" {
|
|
// Check if user is admin (simplified check)
|
|
userRole, _ := c.Get("user_role")
|
|
if userRole != "admin" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Cannot update approved expense"})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Update fields
|
|
expense.Title = updateData.Title
|
|
expense.Description = updateData.Description
|
|
expense.Category = updateData.Category
|
|
expense.Subcategory = updateData.Subcategory
|
|
expense.Amount = updateData.Amount
|
|
expense.Currency = updateData.Currency
|
|
expense.VATRate = updateData.VATRate
|
|
expense.VATAmount = updateData.VATAmount
|
|
expense.TotalAmount = updateData.TotalAmount
|
|
expense.ExpenseDate = updateData.ExpenseDate
|
|
expense.PaymentMethod = updateData.PaymentMethod
|
|
expense.HasReceipt = updateData.HasReceipt
|
|
expense.ReceiptData = updateData.ReceiptData
|
|
expense.ReceiptImage = updateData.ReceiptImage
|
|
expense.BudgetID = updateData.BudgetID
|
|
expense.TeamID = updateData.TeamID
|
|
expense.ProjectID = updateData.ProjectID
|
|
|
|
// Get current user ID from context
|
|
userID, exists := c.Get("user_id")
|
|
if exists {
|
|
expense.UpdatedBy = userID.(uint)
|
|
}
|
|
|
|
if err := fc.db.Save(&expense).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update expense"})
|
|
return
|
|
}
|
|
|
|
// Reload with associations
|
|
if err := fc.db.Preload("Budget").Preload("Documents").First(&expense, expense.ID).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve updated expense"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, expense)
|
|
}
|
|
|
|
// DeleteExpense deletes an expense
|
|
func (fc *FinancialController) DeleteExpense(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var expense models.Expense
|
|
|
|
if err := fc.db.First(&expense, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Expense not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve expense"})
|
|
return
|
|
}
|
|
|
|
// Don't allow deleting approved/reimbursed expenses unless admin
|
|
if expense.Status == "approved" || expense.Status == "reimbursed" {
|
|
userRole, _ := c.Get("user_role")
|
|
if userRole != "admin" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Cannot delete approved expense"})
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := fc.db.Delete(&expense).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete expense"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Expense deleted successfully"})
|
|
}
|
|
|
|
// ApproveExpense approves an expense
|
|
func (fc *FinancialController) ApproveExpense(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var expense models.Expense
|
|
|
|
if err := fc.db.First(&expense, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Expense not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve expense"})
|
|
return
|
|
}
|
|
|
|
if expense.Status != "pending" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Expense is not pending"})
|
|
return
|
|
}
|
|
|
|
// Get current user ID from context
|
|
userID, exists := c.Get("user_id")
|
|
if !exists {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
expense.Status = "approved"
|
|
expense.ApprovedAt = &now
|
|
expense.ApprovedBy = userID.(uint)
|
|
expense.UpdatedBy = userID.(uint)
|
|
|
|
if err := fc.db.Save(&expense).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to approve expense"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, expense)
|
|
}
|
|
|
|
// RejectExpense rejects an expense
|
|
func (fc *FinancialController) RejectExpense(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
var request struct {
|
|
RejectionReason string `json:"rejection_reason" binding:"required"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&request); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var expense models.Expense
|
|
if err := fc.db.First(&expense, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Expense not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve expense"})
|
|
return
|
|
}
|
|
|
|
if expense.Status != "pending" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Expense is not pending"})
|
|
return
|
|
}
|
|
|
|
// Get current user ID from context
|
|
userID, exists := c.Get("user_id")
|
|
if !exists {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
|
|
return
|
|
}
|
|
|
|
expense.Status = "rejected"
|
|
expense.RejectionReason = request.RejectionReason
|
|
expense.UpdatedBy = userID.(uint)
|
|
|
|
if err := fc.db.Save(&expense).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reject expense"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, expense)
|
|
}
|
|
|
|
// UploadReceipt handles receipt upload for expenses
|
|
func (fc *FinancialController) UploadReceipt(c *gin.Context) {
|
|
// This would handle file upload and OCR processing
|
|
// For now, return a placeholder response
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Receipt upload not implemented yet"})
|
|
}
|
|
|
|
// GetExpenseCategories retrieves all unique expense categories
|
|
func (fc *FinancialController) GetExpenseCategories(c *gin.Context) {
|
|
var categories []string
|
|
if err := fc.db.Model(&models.Expense{}).Distinct("category").Pluck("category", &categories).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve categories"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, categories)
|
|
}
|
|
|
|
// GetExpenseOverview provides expense summary and statistics
|
|
func (fc *FinancialController) GetExpenseOverview(c *gin.Context) {
|
|
var overview struct {
|
|
TotalExpenses float64 `json:"total_expenses"`
|
|
PendingExpenses float64 `json:"pending_expenses"`
|
|
ApprovedExpenses float64 `json:"approved_expenses"`
|
|
RejectedExpenses float64 `json:"rejected_expenses"`
|
|
ReimbursedExpenses float64 `json:"reimbursed_expenses"`
|
|
ExpenseCount int `json:"expense_count"`
|
|
ThisMonthExpenses float64 `json:"this_month_expenses"`
|
|
Categories []CategorySummary `json:"categories"`
|
|
}
|
|
|
|
// Get current month boundaries
|
|
now := time.Now()
|
|
startOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
|
|
|
// Calculate totals by status
|
|
fc.db.Model(&models.Expense{}).Select("COALESCE(SUM(total_amount), 0)").Where("status = ?", "pending").Scan(&overview.PendingExpenses)
|
|
fc.db.Model(&models.Expense{}).Select("COALESCE(SUM(total_amount), 0)").Where("status = ?", "approved").Scan(&overview.ApprovedExpenses)
|
|
fc.db.Model(&models.Expense{}).Select("COALESCE(SUM(total_amount), 0)").Where("status = ?", "rejected").Scan(&overview.RejectedExpenses)
|
|
fc.db.Model(&models.Expense{}).Select("COALESCE(SUM(total_amount), 0)").Where("status = ?", "reimbursed").Scan(&overview.ReimbursedExpenses)
|
|
|
|
// Total expenses
|
|
overview.TotalExpenses = overview.PendingExpenses + overview.ApprovedExpenses + overview.ReimbursedExpenses
|
|
|
|
// Count all expenses
|
|
var expenseCount int64
|
|
fc.db.Model(&models.Expense{}).Count(&expenseCount)
|
|
overview.ExpenseCount = int(expenseCount)
|
|
|
|
// This month expenses
|
|
fc.db.Model(&models.Expense{}).Where("expense_date >= ?", startOfMonth).Select("COALESCE(SUM(total_amount), 0)").Scan(&overview.ThisMonthExpenses)
|
|
|
|
// Category summary
|
|
var categories []string
|
|
fc.db.Model(&models.Expense{}).Distinct("category").Pluck("category", &categories)
|
|
|
|
for _, category := range categories {
|
|
var count int64
|
|
var total float64
|
|
fc.db.Model(&models.Expense{}).Where("category = ?", category).Count(&count)
|
|
fc.db.Model(&models.Expense{}).Where("category = ?", category).Select("COALESCE(SUM(total_amount), 0)").Scan(&total)
|
|
|
|
overview.Categories = append(overview.Categories, CategorySummary{
|
|
Category: category,
|
|
BudgetTotal: total, // Using BudgetTotal field for expense total
|
|
SpendTotal: total, // Same for expenses
|
|
BudgetCount: int(count),
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, overview)
|
|
}
|
|
|
|
// Financial Dashboard and Settings
|
|
|
|
// GetFinancialDashboard provides comprehensive financial overview
|
|
func (fc *FinancialController) GetFinancialDashboard(c *gin.Context) {
|
|
var dashboard struct {
|
|
// Budget summary
|
|
BudgetSummary struct {
|
|
TotalBudget float64 `json:"total_budget"`
|
|
TotalSpend float64 `json:"total_spend"`
|
|
RemainingBudget float64 `json:"remaining_budget"`
|
|
BudgetCount int `json:"budget_count"`
|
|
OverBudgetCount int `json:"over_budget_count"`
|
|
} `json:"budget_summary"`
|
|
|
|
// Expense summary
|
|
ExpenseSummary struct {
|
|
TotalExpenses float64 `json:"total_expenses"`
|
|
PendingExpenses float64 `json:"pending_expenses"`
|
|
ApprovedExpenses float64 `json:"approved_expenses"`
|
|
ThisMonthExpenses float64 `json:"this_month_expenses"`
|
|
ExpenseCount int `json:"expense_count"`
|
|
PendingCount int `json:"pending_count"`
|
|
} `json:"expense_summary"`
|
|
|
|
// Sponsorship summary
|
|
SponsorshipSummary struct {
|
|
TotalSponsors int64 `json:"total_sponsors"`
|
|
ActiveSponsors int64 `json:"active_sponsors"`
|
|
TotalValue float64 `json:"total_value"`
|
|
ReceivedThisYear float64 `json:"received_this_year"`
|
|
} `json:"sponsorship_summary"`
|
|
|
|
// Recent activities
|
|
RecentExpenses []models.Expense `json:"recent_expenses"`
|
|
RecentSponsorships []models.Sponsorship `json:"recent_sponsorships"`
|
|
}
|
|
|
|
// Get budget summary
|
|
var budgets []models.Budget
|
|
if err := fc.db.Where("active = ?", true).Find(&budgets).Error; err == nil {
|
|
for _, budget := range budgets {
|
|
dashboard.BudgetSummary.TotalBudget += budget.YearlyLimit
|
|
currentSpend := fc.calculateBudgetSpend(budget.ID)
|
|
dashboard.BudgetSummary.TotalSpend += currentSpend
|
|
if currentSpend > budget.YearlyLimit {
|
|
dashboard.BudgetSummary.OverBudgetCount++
|
|
}
|
|
}
|
|
dashboard.BudgetSummary.BudgetCount = len(budgets)
|
|
dashboard.BudgetSummary.RemainingBudget = dashboard.BudgetSummary.TotalBudget - dashboard.BudgetSummary.TotalSpend
|
|
}
|
|
|
|
// Get expense summary
|
|
fc.db.Model(&models.Expense{}).Select("COALESCE(SUM(total_amount), 0)").Scan(&dashboard.ExpenseSummary.TotalExpenses)
|
|
fc.db.Model(&models.Expense{}).Select("COALESCE(SUM(total_amount), 0)").Where("status = ?", "pending").Scan(&dashboard.ExpenseSummary.PendingExpenses)
|
|
fc.db.Model(&models.Expense{}).Select("COALESCE(SUM(total_amount), 0)").Where("status = ?", "approved").Scan(&dashboard.ExpenseSummary.ApprovedExpenses)
|
|
|
|
var expenseCount, pendingCount int64
|
|
fc.db.Model(&models.Expense{}).Count(&expenseCount)
|
|
fc.db.Model(&models.Expense{}).Where("status = ?", "pending").Count(&pendingCount)
|
|
dashboard.ExpenseSummary.ExpenseCount = int(expenseCount)
|
|
dashboard.ExpenseSummary.PendingCount = int(pendingCount)
|
|
|
|
// This month expenses
|
|
now := time.Now()
|
|
startOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
|
fc.db.Model(&models.Expense{}).Where("expense_date >= ?", startOfMonth).Select("COALESCE(SUM(total_amount), 0)").Scan(&dashboard.ExpenseSummary.ThisMonthExpenses)
|
|
|
|
// Get sponsorship summary
|
|
fc.db.Model(&models.Sponsorship{}).Count(&dashboard.SponsorshipSummary.TotalSponsors)
|
|
fc.db.Model(&models.Sponsorship{}).Where("status = ? AND end_date >= ?", "active", time.Now()).Count(&dashboard.SponsorshipSummary.ActiveSponsors)
|
|
fc.db.Model(&models.Sponsorship{}).Where("status = ?", "active").Select("COALESCE(SUM(total_value), 0)").Scan(&dashboard.SponsorshipSummary.TotalValue)
|
|
|
|
currentYear := now.Year()
|
|
fc.db.Model(&models.SponsorshipPayment{}).
|
|
Where("EXTRACT(YEAR FROM payment_date) = ? AND status = ?", currentYear, "received").
|
|
Select("COALESCE(SUM(amount), 0)").Scan(&dashboard.SponsorshipSummary.ReceivedThisYear)
|
|
|
|
// Get recent expenses (last 10)
|
|
fc.db.Preload("Budget").Order("created_at DESC").Limit(10).Find(&dashboard.RecentExpenses)
|
|
|
|
// Get recent sponsorships (last 5)
|
|
fc.db.Order("created_at DESC").Limit(5).Find(&dashboard.RecentSponsorships)
|
|
|
|
c.JSON(http.StatusOK, dashboard)
|
|
}
|
|
|
|
// Financial Reports Management
|
|
|
|
// GetFinancialReports retrieves all financial reports
|
|
func (fc *FinancialController) GetFinancialReports(c *gin.Context) {
|
|
var reports []models.FinancialReport
|
|
if err := fc.db.Order("created_at DESC").Find(&reports).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve reports"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, reports)
|
|
}
|
|
|
|
// GenerateFinancialReport generates a new financial report
|
|
func (fc *FinancialController) GenerateFinancialReport(c *gin.Context) {
|
|
var request struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Type string `json:"type" binding:"required"`
|
|
Period string `json:"period" binding:"required"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&request); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Get current user ID from context
|
|
userID, exists := c.Get("user_id")
|
|
if !exists {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
|
|
return
|
|
}
|
|
|
|
report := models.FinancialReport{
|
|
Name: request.Name,
|
|
Type: request.Type,
|
|
Period: request.Period,
|
|
GeneratedAt: time.Now(),
|
|
CreatedBy: userID.(uint),
|
|
}
|
|
|
|
// Generate report data based on type
|
|
reportData, summary := fc.generateReportData(request.Type, request.Period)
|
|
report.ReportData = reportData
|
|
report.Summary = summary
|
|
|
|
if err := fc.db.Create(&report).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create report"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, report)
|
|
}
|
|
|
|
// GetFinancialReport retrieves a single financial report
|
|
func (fc *FinancialController) GetFinancialReport(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var report models.FinancialReport
|
|
|
|
if err := fc.db.First(&report, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Report not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve report"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, report)
|
|
}
|
|
|
|
// DeleteFinancialReport deletes a financial report
|
|
func (fc *FinancialController) DeleteFinancialReport(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
if err := fc.db.Delete(&models.FinancialReport{}, id).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete report"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Report deleted successfully"})
|
|
}
|
|
|
|
// Financial Settings Management
|
|
|
|
// GetFinancialSettings retrieves financial settings
|
|
func (fc *FinancialController) GetFinancialSettings(c *gin.Context) {
|
|
var settings models.FinancialSettings
|
|
|
|
// Get or create settings (singleton pattern)
|
|
if err := fc.db.First(&settings).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
// Create default settings
|
|
settings = models.FinancialSettings{
|
|
DefaultCurrency: "CZK",
|
|
DefaultVATRate: 21,
|
|
FiscalYearStart: "01-01",
|
|
ExpenseApprovalRequired: true,
|
|
MaxExpenseAutoApprove: 1000,
|
|
BudgetAlertEnabled: true,
|
|
BudgetAlertThreshold: 80,
|
|
SponsorshipAlertEnabled: true,
|
|
OCRServiceEnabled: true,
|
|
OCRProvider: "tesseract",
|
|
}
|
|
fc.db.Create(&settings)
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve settings"})
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, settings)
|
|
}
|
|
|
|
// UpdateFinancialSettings updates financial settings
|
|
func (fc *FinancialController) UpdateFinancialSettings(c *gin.Context) {
|
|
var settings models.FinancialSettings
|
|
|
|
// Get existing settings
|
|
if err := fc.db.First(&settings).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
// Create if not exists
|
|
if err := c.ShouldBindJSON(&settings); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Get current user ID from context
|
|
userID, exists := c.Get("user_id")
|
|
if exists {
|
|
settings.UpdatedBy = userID.(uint)
|
|
}
|
|
|
|
if err := fc.db.Create(&settings).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create settings"})
|
|
return
|
|
}
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve settings"})
|
|
return
|
|
}
|
|
} else {
|
|
// Update existing
|
|
var updateData models.FinancialSettings
|
|
if err := c.ShouldBindJSON(&updateData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Update fields
|
|
settings.DefaultCurrency = updateData.DefaultCurrency
|
|
settings.DefaultVATRate = updateData.DefaultVATRate
|
|
settings.FiscalYearStart = updateData.FiscalYearStart
|
|
settings.ExpenseApprovalRequired = updateData.ExpenseApprovalRequired
|
|
settings.MaxExpenseAutoApprove = updateData.MaxExpenseAutoApprove
|
|
settings.BudgetAlertEnabled = updateData.BudgetAlertEnabled
|
|
settings.BudgetAlertThreshold = updateData.BudgetAlertThreshold
|
|
settings.SponsorshipAlertEnabled = updateData.SponsorshipAlertEnabled
|
|
settings.OCRServiceEnabled = updateData.OCRServiceEnabled
|
|
settings.OCRProvider = updateData.OCRProvider
|
|
|
|
// Get current user ID from context
|
|
userID, exists := c.Get("user_id")
|
|
if exists {
|
|
settings.UpdatedBy = userID.(uint)
|
|
}
|
|
|
|
if err := fc.db.Save(&settings).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update settings"})
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, settings)
|
|
}
|
|
|
|
// Helper function to generate report data
|
|
func (fc *FinancialController) generateReportData(reportType, period string) (string, string) {
|
|
// This is a placeholder implementation
|
|
// In a real implementation, you would generate comprehensive JSON data based on the report type and period
|
|
reportData := `{
|
|
"expenses": {"total": 0, "by_category": {}, "by_month": {}},
|
|
"budgets": {"total": 0, "utilization": {}},
|
|
"sponsorships": {"total": 0, "by_status": {}},
|
|
"generated_at": "` + time.Now().Format(time.RFC3339) + `"
|
|
}`
|
|
|
|
summary := fmt.Sprintf("Financial report for %s - %s", period, reportType)
|
|
|
|
return reportData, summary
|
|
}
|