package com.mydreamjob.application; import com.mydreamjob.audit.AuditContext; import com.mydreamjob.audit.AuditService; import com.mydreamjob.common.exception.BusinessException; import com.mydreamjob.common.exception.ErrorCode; import com.mydreamjob.company.Company; import com.mydreamjob.company.CompanyRepository; import com.mydreamjob.company.VerifiedCompanyGuard; import com.mydreamjob.job.Job; import com.mydreamjob.job.JobService; import com.mydreamjob.notification.NotificationService; import com.mydreamjob.security.UserRoleGuard; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.Clock; import java.time.LocalDateTime; import java.util.Set; @Service @RequiredArgsConstructor public class ApplicationAcceptanceService { private final UserRoleGuard roles; private final CompanyRepository companies; private final ApplicationRepository applications; private final JobService jobs; private final ApplicationStatusHistoryRepository history; private final AuditService audit; private final NotificationService notifications; private final Clock clock; /** 与撤回/状态推进使用相同投递行锁;重试只产生一次接受记录和通知。 */ @Transactional public Application accept(Long actorUserId, Long applicationId, AuditContext context) { roles.requireCompany(actorUserId); Company company = VerifiedCompanyGuard.requireVerified(companies.findByOwnerUserId(actorUserId).orElse(null)); Application app = applications.findLockedById(applicationId) .orElseThrow(() -> new BusinessException(ErrorCode.APPLICATION_NOT_FOUND)); Job job = jobs.getJob(app.getJobId()); if (!company.getId().equals(job.getCompanyId())) throw new BusinessException(ErrorCode.APPLICATION_OWNER_MISMATCH); if (!Set.of("SUBMITTED","VIEWED","INTERVIEW","HIRED").contains(app.getStatus())) throw new BusinessException(ErrorCode.INVALID_APPLICATION_STATUS_TRANSITION); // 已录用仍可补记明确接受;已经接受时不覆盖原始时间/审计。 if (app.getCompanyAcceptedAt() != null) return app; app.setCompanyAcceptedAt(LocalDateTime.now(clock).truncatedTo(java.time.temporal.ChronoUnit.MICROS)); app = applications.save(app); history.save(ApplicationStatusHistory.builder().applicationId(app.getId()) .operatorUserId(actorUserId).fromStatus(app.getStatus()).toStatus(app.getStatus()) .reason("COMPANY_ACCEPTED: company accepted application; communication unlocked").build()); audit.log(actorUserId,"ACCEPT_APPLICATION","APPLICATION",app.getId(), "Company accepted application; communication unlocked",context); notifications.publishEvent(app.getStudentUserId(),"APPLICATION_ACCEPTED","企业已接受投递", "企业已接受你的投递,可以开始沟通","APPLICATION",app.getId(),"accept-application-"+app.getId()); return app; } }