Initial commit: proyecto ContabilidadSaPolar completo

This commit is contained in:
root
2026-08-19 21:30:23 +00:00
commit 0d5c6f9512
232 changed files with 26730 additions and 0 deletions
@@ -0,0 +1,87 @@
package com.sapolar.maintenance;
import com.sapolar.auth.SecurityUser;
import com.sapolar.common.dto.ApiResponse;
import com.sapolar.finance.expense.ExpenseCategory;
import com.sapolar.finance.expense.ExpenseCategoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
@RestController
@RequestMapping("/api/maintenance")
@RequiredArgsConstructor
public class MaintenanceController {
private final MaintenanceService maintenanceService;
private final ExpenseCategoryRepository expenseCategoryRepository;
@GetMapping
public ResponseEntity<ApiResponse<List<ScheduledMaintenance>>> findAll(
@RequestParam(required = false) Long propertyId) {
if (propertyId != null) {
return ResponseEntity.ok(ApiResponse.success(maintenanceService.findByProperty(propertyId)));
}
return ResponseEntity.ok(ApiResponse.success(maintenanceService.findAll()));
}
@GetMapping("/pending")
public ResponseEntity<ApiResponse<List<ScheduledMaintenance>>> getPending() {
return ResponseEntity.ok(ApiResponse.success(maintenanceService.findPending()));
}
@GetMapping("/upcoming")
public ResponseEntity<ApiResponse<List<ScheduledMaintenance>>> getUpcoming(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to) {
return ResponseEntity.ok(ApiResponse.success(maintenanceService.findUpcoming(from, to)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<ScheduledMaintenance>> findById(@PathVariable Long id) {
return ResponseEntity.ok(ApiResponse.success(maintenanceService.findById(id)));
}
@GetMapping("/expense-categories")
public ResponseEntity<ApiResponse<List<ExpenseCategory>>> getExpenseCategories() {
return ResponseEntity.ok(ApiResponse.success(expenseCategoryRepository.findAll()));
}
@PostMapping
public ResponseEntity<ApiResponse<ScheduledMaintenance>> create(@RequestBody ScheduledMaintenance maintenance,
@AuthenticationPrincipal SecurityUser user) {
return ResponseEntity.ok(ApiResponse.success("Mantenimiento creado",
maintenanceService.create(maintenance, user.userId())));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<ScheduledMaintenance>> update(@PathVariable Long id,
@RequestBody ScheduledMaintenance maintenance) {
return ResponseEntity.ok(ApiResponse.success("Mantenimiento actualizado",
maintenanceService.update(id, maintenance)));
}
@PatchMapping("/{id}/complete")
public ResponseEntity<ApiResponse<ScheduledMaintenance>> complete(@PathVariable Long id,
@AuthenticationPrincipal SecurityUser user) {
return ResponseEntity.ok(ApiResponse.success("Mantenimiento completado",
maintenanceService.markCompleted(id, user.userId())));
}
@PatchMapping("/{id}/reopen")
public ResponseEntity<ApiResponse<ScheduledMaintenance>> reopen(@PathVariable Long id) {
return ResponseEntity.ok(ApiResponse.success("Mantenimiento reabierto",
maintenanceService.reopen(id)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long id) {
maintenanceService.delete(id);
return ResponseEntity.ok(ApiResponse.success("Mantenimiento eliminado", null));
}
}
@@ -0,0 +1,19 @@
package com.sapolar.maintenance;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "maintenance_periods")
public class MaintenancePeriod {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(nullable = false, unique = true, length = 30)
private String name;
}
@@ -0,0 +1,190 @@
package com.sapolar.maintenance;
import com.sapolar.common.exception.ResourceNotFoundException;
import com.sapolar.finance.expense.ExpenseCategory;
import com.sapolar.finance.expense.ExpenseReceipt;
import com.sapolar.finance.expense.ExpenseReceiptRepository;
import com.sapolar.finance.expense.ExpenseStatus;
import com.sapolar.property.Property;
import com.sapolar.property.PropertyGroup;
import com.sapolar.property.PropertyGroupRepository;
import com.sapolar.property.PropertyRepository;
import com.sapolar.user.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
@Service
@RequiredArgsConstructor
public class MaintenanceService {
private final ScheduledMaintenanceRepository maintenanceRepository;
private final PropertyRepository propertyRepository;
private final PropertyGroupRepository propertyGroupRepository;
private final ExpenseReceiptRepository expenseReceiptRepository;
public List<ScheduledMaintenance> findAll() {
return maintenanceRepository.findAll();
}
public List<ScheduledMaintenance> findByProperty(Long propertyId) {
return maintenanceRepository.findByPropertyId(propertyId);
}
public List<ScheduledMaintenance> findPending() {
return maintenanceRepository.findByCompletedFalse();
}
public List<ScheduledMaintenance> findUpcoming(LocalDate start, LocalDate end) {
return maintenanceRepository.findByCompletedFalseAndNextExecutionBetween(start, end);
}
public ScheduledMaintenance findById(Long id) {
return maintenanceRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Mantenimiento", id));
}
@Transactional
public ScheduledMaintenance create(ScheduledMaintenance maintenance, Long userId) {
// Asociar propiedad o conjunto
if (maintenance.getProperty() != null && maintenance.getProperty().getId() != null) {
Property property = propertyRepository.findById(maintenance.getProperty().getId())
.orElseThrow(() -> new ResourceNotFoundException("Propiedad", maintenance.getProperty().getId()));
maintenance.setProperty(property);
}
if (maintenance.getPropertyGroup() != null && maintenance.getPropertyGroup().getId() != null) {
PropertyGroup group = propertyGroupRepository.findById(maintenance.getPropertyGroup().getId())
.orElseThrow(() -> new ResourceNotFoundException("Conjunto", maintenance.getPropertyGroup().getId()));
maintenance.setPropertyGroup(group);
}
User user = new User();
user.setId(userId);
maintenance.setCreatedBy(user);
maintenance.setCompleted(false);
ScheduledMaintenance saved = maintenanceRepository.save(maintenance);
// Auto-generar gasto si la opción está activa
if (Boolean.TRUE.equals(saved.getGenerateExpense()) && saved.getExpenseCategory() != null) {
createExpenseFromMaintenance(saved, userId);
}
return saved;
}
@Transactional
public ScheduledMaintenance update(Long id, ScheduledMaintenance updated) {
ScheduledMaintenance maintenance = findById(id);
maintenance.setTitle(updated.getTitle());
maintenance.setDescription(updated.getDescription());
maintenance.setEstimatedCost(updated.getEstimatedCost());
maintenance.setNextExecution(updated.getNextExecution());
maintenance.setReminderDaysBefore(updated.getReminderDaysBefore());
maintenance.setResponsible(updated.getResponsible());
maintenance.setNotes(updated.getNotes());
maintenance.setGenerateExpense(updated.getGenerateExpense());
if (updated.getPeriod() != null) maintenance.setPeriod(updated.getPeriod());
if (updated.getExpenseCategory() != null) maintenance.setExpenseCategory(updated.getExpenseCategory());
return maintenanceRepository.save(maintenance);
}
@Transactional
public ScheduledMaintenance markCompleted(Long id, Long userId) {
ScheduledMaintenance maintenance = findById(id);
maintenance.setCompleted(true);
maintenance.setCompletedAt(LocalDate.now());
User user = new User();
user.setId(userId);
maintenance.setCompletedBy(user);
ScheduledMaintenance saved = maintenanceRepository.save(maintenance);
// Auto-generar gasto al completar si la opción está activa y es periódico
if (Boolean.TRUE.equals(saved.getGenerateExpense()) && saved.getExpenseCategory() != null) {
// Para periódicos (no única vez): generar gasto cada vez que se completa
if (saved.getPeriod() != null && saved.getPeriod().getId() != 1) {
createExpenseFromMaintenance(saved, userId);
}
}
return saved;
}
@Transactional
public ScheduledMaintenance reopen(Long id) {
ScheduledMaintenance maintenance = findById(id);
maintenance.setCompleted(false);
maintenance.setCompletedAt(null);
maintenance.setCompletedBy(null);
return maintenanceRepository.save(maintenance);
}
@Transactional
public void delete(Long id) {
maintenanceRepository.deleteById(id);
}
/**
* Crea un gasto a partir de los datos de una tarea de mantenimiento.
*/
private ExpenseReceipt createExpenseFromMaintenance(ScheduledMaintenance maintenance, Long userId) {
ExpenseReceipt expense = new ExpenseReceipt();
// Propiedad o conjunto asociado
if (maintenance.getProperty() != null && maintenance.getProperty().getId() != null) {
Property propertyRef = new Property();
propertyRef.setId(maintenance.getProperty().getId());
expense.setProperty(propertyRef);
}
if (maintenance.getPropertyGroup() != null && maintenance.getPropertyGroup().getId() != null) {
PropertyGroup groupRef = new PropertyGroup();
groupRef.setId(maintenance.getPropertyGroup().getId());
expense.setPropertyGroup(groupRef);
}
// Categoría de gasto
expense.setCategory(maintenance.getExpenseCategory());
// Estado PENDIENTE (id=1)
ExpenseStatus status = new ExpenseStatus();
status.setId(1);
expense.setStatus(status);
// Importes
BigDecimal amount = maintenance.getEstimatedCost() != null
? maintenance.getEstimatedCost()
: BigDecimal.ZERO;
expense.setAmount(amount);
expense.setTaxAmount(BigDecimal.ZERO);
expense.setTotalAmount(amount);
// Fechas
expense.setIssueDate(LocalDate.now());
if (maintenance.getNextExecution() != null) {
expense.setDueDate(maintenance.getNextExecution());
}
// Descripción
String desc = "Mantenimiento: " + maintenance.getTitle();
if (maintenance.getProperty() != null && maintenance.getProperty().getName() != null) {
desc += " - " + maintenance.getProperty().getName();
}
expense.setDescription(desc);
// Responsable como proveedor
expense.setSupplierName(maintenance.getResponsible());
// Creador
User user = new User();
user.setId(userId);
expense.setCreatedBy(user);
return expenseReceiptRepository.save(expense);
}
}
@@ -0,0 +1,102 @@
package com.sapolar.maintenance;
import com.sapolar.finance.expense.ExpenseCategory;
import com.sapolar.property.Property;
import com.sapolar.property.PropertyGroup;
import com.sapolar.user.User;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Getter
@Setter
@Entity
@Table(name = "scheduled_maintenance", indexes = {
@Index(name = "idx_maint_property", columnList = "property_id"),
@Index(name = "idx_maint_next_exec", columnList = "next_execution"),
@Index(name = "idx_maint_completed", columnList = "completed")
})
public class ScheduledMaintenance {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "property_id")
private Property property;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "property_group_id")
private PropertyGroup propertyGroup;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "period_id", nullable = false)
private MaintenancePeriod period;
@Column(nullable = false, length = 200)
private String title;
@Column(columnDefinition = "TEXT")
private String description;
@Column(name = "estimated_cost", precision = 12, scale = 2)
private BigDecimal estimatedCost;
@Column(name = "last_execution")
private LocalDate lastExecution;
@Column(name = "next_execution", nullable = false)
private LocalDate nextExecution;
@Column(name = "reminder_days_before", nullable = false)
private Integer reminderDaysBefore = 30;
@Column(length = 200)
private String responsible;
@Column(columnDefinition = "TEXT")
private String notes;
@Column(name = "generate_expense", nullable = false)
private Boolean generateExpense = false;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "expense_category_id")
private ExpenseCategory expenseCategory;
@Column(nullable = false)
private Boolean completed = false;
@Column(name = "completed_at")
private LocalDate completedAt;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "completed_by")
private User completedBy;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "created_by")
private User createdBy;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
@@ -0,0 +1,15 @@
package com.sapolar.maintenance;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
@Repository
public interface ScheduledMaintenanceRepository extends JpaRepository<ScheduledMaintenance, Long> {
List<ScheduledMaintenance> findByPropertyId(Long propertyId);
List<ScheduledMaintenance> findByCompletedFalse();
List<ScheduledMaintenance> findByCompletedFalseAndNextExecutionBetween(LocalDate start, LocalDate end);
List<ScheduledMaintenance> findByCompletedFalseAndNextExecutionBefore(LocalDate date);
}