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#
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를 비동기로 실행합니다.
# 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!는 실행되지 않습니다.
# 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__c에 model.team.sf_resource_id를 설정합니다. 이 값이 Opportunity의 Account와 다르면 Salesforce validation rule에 의해 거부됩니다.
# 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로 반환합니다.
# 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 쿼리:
service:cupixworks-worker status:error "Fail to create salesforce Facility resource" @environment:production
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. |
{
"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분 전):
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 누락으로 인한 업데이트 실패:
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 에러에 대한 별도 알림:
service:cupixworks-worker "FIELD_CUSTOM_VALIDATION_EXCEPTION"
sf_resource_id가 nil인 Facility 수 추적:
service:cupixworks-worker "Skip updating salesforce facility resource because sf_resource_id is blank"
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard
- Salesforce CRM 측 데이터 문제가 주 원인이며, 코드 변경만으로는 완전히 해결되지 않음. 단,
sf_resource_idnil 상태가 누적되면 Salesforce 동기화 전반에 영향을 줄 수 있어 장기적으로 복구 메커니즘이 필요.