@Override
  @PlusTransactional
  public ResponseEvent<DistributionProtocolDetail> updateDistributionProtocol(
      RequestEvent<DistributionProtocolDetail> req) {
    try {
      AccessCtrlMgr.getInstance().ensureUserIsAdmin();

      Long protocolId = req.getPayload().getId();
      String title = req.getPayload().getTitle();

      DistributionProtocol existing = null;
      if (protocolId != null) {
        existing = daoFactory.getDistributionProtocolDao().getById(protocolId);
      } else {
        existing = daoFactory.getDistributionProtocolDao().getDistributionProtocol(title);
      }

      if (existing == null) {
        return ResponseEvent.userError(DistributionProtocolErrorCode.NOT_FOUND);
      }

      DistributionProtocol distributionProtocol =
          distributionProtocolFactory.createDistributionProtocol(req.getPayload());
      ensureUniqueConstraints(distributionProtocol, existing);

      existing.update(distributionProtocol);
      daoFactory.getDistributionProtocolDao().saveOrUpdate(existing);
      return ResponseEvent.response(DistributionProtocolDetail.from(existing));
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception ex) {
      return ResponseEvent.serverError(ex);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<DpRequirementDetail> updateRequirement(
      RequestEvent<DpRequirementDetail> req) {
    try {
      AccessCtrlMgr.getInstance().ensureUserIsAdmin();

      Long dpReqId = req.getPayload().getId();
      DpRequirement existing = getDprDao().getById(dpReqId);
      if (existing == null) {
        return ResponseEvent.userError(DpRequirementErrorCode.NOT_FOUND);
      }

      DpRequirement newDpr = dprFactory.createDistributionProtocolRequirement(req.getPayload());
      OpenSpecimenException ose = new OpenSpecimenException(ErrorType.USER_ERROR);
      ensureUniqueReqConstraints(existing, newDpr, ose);
      ose.checkAndThrow();

      existing.update(newDpr);
      getDprDao().saveOrUpdate(existing);
      return ResponseEvent.response(DpRequirementDetail.from(existing));
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<List<DistributionProtocolDetail>> getDistributionProtocols(
      RequestEvent<DpListCriteria> req) {
    try {
      DpListCriteria crit = req.getPayload();
      Set<Long> siteIds = AccessCtrlMgr.getInstance().getReadAccessDistributionOrderSites();
      if (siteIds != null && CollectionUtils.isEmpty(siteIds)) {
        return ResponseEvent.userError(RbacErrorCode.ACCESS_DENIED);
      }

      if (siteIds != null) {
        crit.siteIds(siteIds);
      }

      List<DistributionProtocol> dps =
          daoFactory.getDistributionProtocolDao().getDistributionProtocols(crit);
      List<DistributionProtocolDetail> result = DistributionProtocolDetail.from(dps);

      if (crit.includeStat()) {
        addDpStats(result);
      }

      return ResponseEvent.response(result);
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<List<DpRequirementDetail>> getRequirements(RequestEvent<Long> req) {
    try {
      AccessCtrlMgr.getInstance().ensureUserIsAdmin();
      Long dpId = req.getPayload();
      DistributionProtocol dp = daoFactory.getDistributionProtocolDao().getById(dpId);
      if (dp == null) {
        return ResponseEvent.userError(DistributionProtocolErrorCode.NOT_FOUND);
      }

      List<DpRequirementDetail> reqDetails = DpRequirementDetail.from(dp.getRequirements());
      Map<Long, BigDecimal> distributedQty = getDprDao().getDistributedQtyByDp(dpId);
      for (DpRequirementDetail reqDetail : reqDetails) {
        BigDecimal qty = distributedQty.get(reqDetail.getId());
        reqDetail.setDistributedQty(qty == null ? BigDecimal.ZERO : qty);
      }

      return ResponseEvent.response(reqDetails);
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
 @Override
 @PlusTransactional
 public ResponseEvent<List<AuthDomainSummary>> getDomains(
     RequestEvent<ListAuthDomainCriteria> req) {
   List<AuthDomain> authDomains =
       daoFactory.getAuthDao().getAuthDomains(req.getPayload().maxResults());
   return ResponseEvent.response(AuthDomainSummary.from(authDomains));
 }
  @Override
  @PlusTransactional
  public ResponseEvent<List<DistributionOrderStat>> getOrderStats(
      RequestEvent<DistributionOrderStatListCriteria> req) {
    try {
      DistributionOrderStatListCriteria crit = req.getPayload();
      List<DistributionOrderStat> stats = getOrderStats(crit);

      return ResponseEvent.response(stats);
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<DistributionProtocolDetail> updateActivityStatus(
      RequestEvent<DistributionProtocolDetail> req) {
    try {
      AccessCtrlMgr.getInstance().ensureUserIsAdmin();

      Long dpId = req.getPayload().getId();
      String status = req.getPayload().getActivityStatus();
      if (StringUtils.isBlank(status) || !Status.isValidActivityStatus(status)) {
        return ResponseEvent.userError(ActivityStatusErrorCode.INVALID);
      }

      DistributionProtocol existing = daoFactory.getDistributionProtocolDao().getById(dpId);
      if (existing == null) {
        return ResponseEvent.userError(DistributionProtocolErrorCode.NOT_FOUND);
      }

      if (existing.getActivityStatus().equals(status)) {
        return ResponseEvent.response(DistributionProtocolDetail.from(existing));
      }

      if (status.equals(Status.ACTIVITY_STATUS_DISABLED.getStatus())) {
        existing.delete();
      } else {
        existing.setActivityStatus(status);
      }

      daoFactory.getDistributionProtocolDao().saveOrUpdate(existing);
      return ResponseEvent.response(DistributionProtocolDetail.from(existing));
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<List<DependentEntityDetail>> getDependentEntities(RequestEvent<Long> req) {
    try {
      DistributionProtocol existing =
          daoFactory.getDistributionProtocolDao().getById(req.getPayload());
      if (existing == null) {
        return ResponseEvent.userError(DistributionProtocolErrorCode.NOT_FOUND);
      }

      return ResponseEvent.response(existing.getDependentEntities());
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<File> exportOrderStats(RequestEvent<DistributionOrderStatListCriteria> req) {
    File tempFile = null;
    CSVWriter csvWriter = null;
    try {
      DistributionOrderStatListCriteria crit = req.getPayload();
      List<DistributionOrderStat> orderStats = getOrderStats(crit);

      tempFile = File.createTempFile("dp-order-stats", null);
      csvWriter = new CSVWriter(new FileWriter(tempFile));

      if (crit.dpId() != null && !orderStats.isEmpty()) {
        DistributionOrderStat orderStat = orderStats.get(0);
        csvWriter.writeNext(
            new String[] {
              MessageUtil.getInstance().getMessage("dist_dp_title"),
              orderStat.getDistributionProtocol().getTitle()
            });
      }

      csvWriter.writeNext(
          new String[] {
            MessageUtil.getInstance().getMessage("dist_exported_by"),
            AuthUtil.getCurrentUser().formattedName()
          });
      csvWriter.writeNext(
          new String[] {
            MessageUtil.getInstance().getMessage("dist_exported_on"),
            Utility.getDateString(Calendar.getInstance().getTime())
          });
      csvWriter.writeNext(new String[1]);

      String[] headers = getOrderStatsReportHeaders(crit);
      csvWriter.writeNext(headers);
      for (DistributionOrderStat stat : orderStats) {
        csvWriter.writeNext(getOrderStatsReportData(stat, crit));
      }

      return ResponseEvent.response(tempFile);
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    } finally {
      IOUtils.closeQuietly(csvWriter);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<AuthDomainDetail> registerDomain(RequestEvent<AuthDomainDetail> req) {
    try {
      AuthDomainDetail detail = req.getPayload();
      AuthDomain authDomain = domainRegFactory.createDomain(detail);

      ensureUniqueDomainName(authDomain.getName());
      daoFactory.getAuthDao().saveOrUpdate(authDomain);
      return ResponseEvent.response(AuthDomainDetail.from(authDomain));
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<DpRequirementDetail> getRequirement(RequestEvent<Long> req) {
    try {
      AccessCtrlMgr.getInstance().ensureUserIsAdmin();
      DpRequirement existing = getDprDao().getById(req.getPayload());
      if (existing == null) {
        return ResponseEvent.userError(DpRequirementErrorCode.NOT_FOUND);
      }

      return ResponseEvent.response(DpRequirementDetail.from(existing));
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<DistributionProtocolDetail> getDistributionProtocol(RequestEvent<Long> req) {
    try {
      Long protocolId = req.getPayload();
      DistributionProtocol existing = daoFactory.getDistributionProtocolDao().getById(protocolId);
      if (existing == null) {
        return ResponseEvent.userError(DistributionProtocolErrorCode.NOT_FOUND);
      }

      AccessCtrlMgr.getInstance().ensureReadDPRights(existing);
      return ResponseEvent.response(DistributionProtocolDetail.from(existing));
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<DistributionProtocolDetail> createDistributionProtocol(
      RequestEvent<DistributionProtocolDetail> req) {
    try {
      AccessCtrlMgr.getInstance().ensureUserIsAdmin();

      DistributionProtocol dp =
          distributionProtocolFactory.createDistributionProtocol(req.getPayload());
      ensureUniqueConstraints(dp, null);

      daoFactory.getDistributionProtocolDao().saveOrUpdate(dp);
      return ResponseEvent.response(DistributionProtocolDetail.from(dp));
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
  @PlusTransactional
  public ResponseEvent<AuthDomainDetail> updateDomain(RequestEvent<AuthDomainDetail> req) {
    try {
      AuthDomainDetail detail = req.getPayload();
      AuthDomain existingDomain = daoFactory.getAuthDao().getAuthDomainByName(detail.getName());
      if (existingDomain == null) {
        throw OpenSpecimenException.userError(AuthProviderErrorCode.DOMAIN_NOT_FOUND);
      }

      AuthDomain authDomain = domainRegFactory.createDomain(detail);
      existingDomain.update(authDomain);

      return ResponseEvent.response(AuthDomainDetail.from(existingDomain));
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<DpRequirementDetail> createRequirement(
      RequestEvent<DpRequirementDetail> req) {
    try {
      AccessCtrlMgr.getInstance().ensureUserIsAdmin();

      DpRequirement dpr = dprFactory.createDistributionProtocolRequirement(req.getPayload());

      OpenSpecimenException ose = new OpenSpecimenException(ErrorType.USER_ERROR);
      ensureUniqueReqConstraints(null, dpr, ose);
      ose.checkAndThrow();

      getDprDao().saveOrUpdate(dpr);
      return ResponseEvent.response(DpRequirementDetail.from(dpr));
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }
  @Override
  @PlusTransactional
  public ResponseEvent<DistributionProtocolDetail> deleteDistributionProtocol(
      RequestEvent<Long> req) {
    try {
      AccessCtrlMgr.getInstance().ensureUserIsAdmin();

      DistributionProtocol existing =
          daoFactory.getDistributionProtocolDao().getById(req.getPayload());
      if (existing == null) {
        return ResponseEvent.userError(DistributionProtocolErrorCode.NOT_FOUND);
      }

      existing.delete();
      daoFactory.getDistributionProtocolDao().saveOrUpdate(existing);
      return ResponseEvent.response(DistributionProtocolDetail.from(existing));
    } catch (OpenSpecimenException ose) {
      return ResponseEvent.error(ose);
    } catch (Exception e) {
      return ResponseEvent.serverError(e);
    }
  }