ES /docs

Fail to create salesforce Facility resource. reason: FIELD_CUSTOM_VALIDATION_EXCEPTION: The Account

RCA: Fail to create salesforce Facility resource - FIELD_CUSTOM_VALIDATION_EXCEPTION

Error Log#

Datadog Logs

text
Fail to create salesforce Facility resource. reason: FIELD_CUSTOM_VALIDATION_EXCEPTION: The Account on the Associated Opportunity and the Account on this Project Record Don't Match.
RESPONSE: [{"message":"The Account on the Associated Opportunity and the Account on this Project Record Don't Match.","errorCode":"FIELD_CUSTOM_VALIDATION_EXCEPTION","fields":[]}]

Impact#

  • Service: cupixworks-worker
  • 발생 횟수: 1
  • 최초 발생: 2026-04-20T06:38:48.229Z
  • 최근 발생: 2026-04-20T06:38:48.229Z

Root Cause Summary#

Salesforce의 Project__c 객체에 설정된 커스텀 validation rule이 Facility 생성 요청을 거부하였습니다. Facility 생성 시 Account__c 필드에 Team의 Salesforce Account ID(team.sf_resource_id)를 설정하는데, 이 Account가 해당 Facility에 연결된 Opportunity의 Account와 일치하지 않아 Salesforce API에서 FIELD_CUSTOM_VALIDATION_EXCEPTION이 발생합니다. 이는 코드 버그가 아닌 Salesforce CRM 측 데이터 정합성 문제입니다. 코드에서는 Facility 생성 전에 Account-Opportunity 간 일치 여부를 검증하지 않으며, API 에러 발생 시 예외를 삼키고(swallow) sf_resource_id를 nil로 남겨둡니다.

Technical Analysis#

Code Path#

1. Facility 생성 시 after_commit 콜백으로 worker 큐잉

sf_resource_id가 비어 있으면 SalesforceResourceCreateWorker를 비동기로 실행합니다.

ruby
# app/models/concerns/salesforce_integratable/facility.rb:14-21
after_commit :create_salesforce_resource_in_worker, if: :create_salesforce_resource?, on: :create

def create_salesforce_resource?
  self.sf_resource_id.blank?
end

def create_salesforce_resource_in_worker
  SalesforceResourceCreateWorker.perform_async(self.id, self.class.name)
end

2. Worker가 SalesforceService를 통해 Facility Client 호출

Worker는 model을 로드하고 create_resource!를 호출합니다. 에러 발생 시 rescue로 잡아 로깅만 하고 종료합니다. facility_resource_id가 nil이므로 model.update!는 실행되지 않습니다.

ruby
# app/workers/salesforce_resource_create_worker.rb:5-14
def perform(model_id, model_type)
  Cupix::Logger.info("Start creating salesforce object resource in worker. model_type: #{model_type}, model_id: #{model_id}", class: 'SalesforceResourceCreateWorker', function: __method__)
  model = model_type.constantize.find(model_id)
  facility_resource_id = Cupix::Salesforce::SalesforceService.create_resource!(model)
  model.update!(sf_resource_id: facility_resource_id) if model.respond_to?(:sf_resource_id)
rescue => e
  Cupix::Logger.error("exception occurred while creating salesforce object resource in worker. model_type: #{model_type}, model_id: #{model_id}, reason: #{e.message}", class: 'SalesforceResourceCreateWorker', function: __method__)
else
  Cupix::Logger.info("finish creating salesforce object resource in worker. model_type: #{model_type}, model_id: #{model_id}", class: 'SalesforceResourceCreateWorker', function: __method__)
end

3. Facility Client가 Team의 Account ID를 사용해 Salesforce API 호출

Account__cmodel.team.sf_resource_id를 설정합니다. 이 값이 Opportunity의 Account와 다르면 Salesforce validation rule에 의해 거부됩니다.

ruby
# app/services/cupix/salesforce/client/facility.rb:10-21
def parent_resource_id(model)
  model.team.sf_resource_id
end

def create_params(model)
  {
    Account__c: parent_resource_id(model),
    Name: model.name,
    Project_ID__c: model.id,
    CupixWorks_Team_Domain__c: model.team.domain
  }
end

4. Base Client에서 Salesforce API 호출 및 에러 처리 (Failure point)

@client.create!가 Salesforce의 validation exception을 raise하면, rescue 블록에서 에러를 로깅하고 resource_id를 nil로 반환합니다.

ruby
# app/services/cupix/salesforce/client/base.rb:14-30
def create_resource!(model)
  Cupix::Logger.info("Start creating salesforce #{model.class.name} resource.", class: self.class.name, function: __method__, model: log_meta(model))

  if parent_resource_id(model).blank?
    Cupix::Logger.warn('Skip creating salesforce resource because parent_resource_id is blank.', class: self.class.name, function: __method__, model: log_meta(model))
    return
  end

  begin
    resource_id = @client.create!(sobject_name, create_params(model))
    Cupix::Logger.info("Salesforce #{model.class.name} resource is created successful. salesforce_resource_id: #{resource_id}", class: self.class.name, function: __method__, model: log_meta(model))
  rescue => e
    Cupix::Logger.error("Fail to create salesforce #{model.class.name} resource. reason: #{e.message}", class: self.class.name, function: __method__, model: log_meta(model))
  end

  resource_id
end

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-worker status:error "Fail to create salesforce Facility resource" @environment:production
text
service:cupixworks-worker "salesforce" @request_id:1113228b9f7bc4111c7d7e50

Production 에러 타임라인 (Facility ID 19995, request_id: 1113228b9f7bc4111c7d7e50):

동일 request_id로 5개의 로그가 2026-04-20T06:38:48.229Z에 기록되었습니다:

순서 Level Class Function Message
1 info SalesforceResourceCreateWorker perform Start creating salesforce object resource in worker. model_type: Facility, model_id: 19995
2 info Cupix::Salesforce::Client::Facility create_resource! Start creating salesforce Facility resource.
3 error Cupix::Salesforce::Client::Facility create_resource! Fail to create salesforce Facility resource. reason: FIELD_CUSTOM_VALIDATION_EXCEPTION...
4 warn Facility _update_document NotFound - attributes_in_database
5 info SalesforceResourceCreateWorker perform finish creating salesforce object resource in worker.
json
{
  "message": "Fail to create salesforce Facility resource. reason: FIELD_CUSTOM_VALIDATION_EXCEPTION: The Account on the Associated Opportunity and the Account on this Project Record Don't Match.\nRESPONSE: [{\"message\":\"The Account on the Associated Opportunity and the Account on this Project Record Don't Match.\",\"errorCode\":\"FIELD_CUSTOM_VALIDATION_EXCEPTION\",\"fields\":[]}]",
  "class": "Cupix::Salesforce::Client::Facility",
  "function": "create_resource!",
  "model": {"type": "Facility", "id": 19995, "name": "Test2 Project"}
}

QA 환경에서도 동일 에러 발생 (33분 전):

text
service:cupixworks-worker status:error "FIELD_CUSTOM_VALIDATION_EXCEPTION" @environment:qa
  • Timestamp: 2026-04-20T06:05:48.273Z
  • Facility ID: 8314 ("Untitled Project")
  • QA에서도 동일한 Account-Opportunity 불일치 에러 발생 — 체계적 문제임을 시사

후속 영향 — sf_resource_id 누락으로 인한 업데이트 실패:

text
service:cupixworks-worker status:warn "Skip updating salesforce facility resource because sf_resource_id is blank"
  • Facility 19671 (2026-04-20T06:39:58.241Z, us-west-2): 업데이트 스킵
  • Facility 2407 (2026-04-20T06:36:36.343Z, ap-southeast-2): 업데이트 스킵

이전 create_resource! 실패로 sf_resource_id가 nil인 Facility들이 이후 업데이트도 불가능한 상태입니다.

Fix Recommendation#

즉시 조치 (Critical)#

  • app/services/cupix/salesforce/client/base.rb:25-26: FIELD_CUSTOM_VALIDATION_EXCEPTION 에러를 warn 레벨로 로깅하는 것을 검토. Salesforce 측 validation rule 위반은 코드 에러가 아닌 데이터 정합성 문제이므로 error 레벨이 적절한지 평가 필요.
  • Salesforce CRM 관리자에게 확인: Facility 19995 ("Test2 Project")에 연결된 Opportunity의 Account와 Team의 Account(team.sf_resource_id)가 불일치하는 원인 파악. CRM 측 데이터 수정 또는 validation rule 조정 여부 결정.

단기 개선 (1주 이내)#

  • app/services/cupix/salesforce/client/facility.rb: Facility 생성 전에 관련 Opportunity의 Account ID와 team.sf_resource_id를 비교하는 사전 검증 로직 추가. 불일치 시 Salesforce API 호출 전에 warn 로깅 후 스킵하거나, 올바른 Account를 선택하는 로직 구현.
  • app/workers/salesforce_resource_create_worker.rb: FIELD_CUSTOM_VALIDATION_EXCEPTION처럼 재시도해도 해결되지 않는 에러에 대해서는 retry를 비활성화하고 적절한 알림 메커니즘 추가.

장기 개선 (재발 방지)#

  • Salesforce 연동 시 Account 매핑 전략을 재검토. 현재는 Team의 sf_resource_id를 그대로 사용하지만, Opportunity가 다른 Account에 속할 수 있는 비즈니스 시나리오를 지원해야 할 수 있음.
  • sf_resource_id가 nil인 Facility들에 대한 정기적인 재동기화(re-sync) 메커니즘 도입. 현재는 초기 생성 실패 시 복구 경로가 없음.

Monitoring#

  • Salesforce validation 에러에 대한 별도 알림:
text
service:cupixworks-worker "FIELD_CUSTOM_VALIDATION_EXCEPTION"
  • sf_resource_id가 nil인 Facility 수 추적:
text
service:cupixworks-worker "Skip updating salesforce facility resource because sf_resource_id is blank"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard
  • Salesforce CRM 측 데이터 문제가 주 원인이며, 코드 변경만으로는 완전히 해결되지 않음. 단, sf_resource_id nil 상태가 누적되면 Salesforce 동기화 전반에 영향을 줄 수 있어 장기적으로 복구 메커니즘이 필요.