소개
Odoo를 다루다 보면 값이 자동으로 계산되는 필드를 자주 만나게 됩니다. 그중에서도 저장된 계산 필드는 계산 결과를 단순히 화면에 보여주는 수준을 넘어서, 그 값을 데이터베이스에 직접 저장해 둡니다.
이 차이는 생각보다 중요합니다. 즉석 계산 필드는 화면에 표시될 때마다 새로 계산되므로 검색·필터·그룹화·내보내기 같은 데이터베이스 기반 기능에서 사용할 수 없습니다. 반면 저장된 계산 필드는 일반 컬럼처럼 데이터베이스에 남아 언제든지 쿼리로 활용할 수 있습니다.
이 가이드는 Odoo에서 저장된 계산 필드를 이해하고 활용하는 데 필요한 핵심 내용을 모두 담고 있습니다. Odoo 데이터 모델 내부 동작, Odoo Studio와 Python으로 필드를 만드는 방법, 실제 비즈니스 사례, 그리고 피해야 할 흔한 실수까지 다룹니다.
Odoo에서 저장된 계산 필드란 무엇인가
Odoo ORM에서는 모델의 각 필드가 데이터를 보관합니다. 대부분의 필드는 사용자가 직접 입력한 값을 저장하지만, 계산 필드는 Python 함수로 값을 생성해 내는 점이 다릅니다.
간단히 말해 저장된 계산 필드는 compute 함수와 함께 store=True로 선언된 계산 필드입니다. 의존 필드가 바뀌면 Odoo가 계산 함수를 실행해 결과를 데이터베이스 컬럼에 씁니다. 그렇게 저장된 값은 다른 일반 필드처럼 사용 가능합니다.
파이썬에서 기본적인 패턴은 다음과 같습니다:
total_amount = fields.Float(
string='Total Amount',
compute='_compute_total_amount',
store=True,
)
@api.depends('quantity', 'unit_price')
def _compute_total_amount(self):
for record in self:
record.total_amount = record.quantity * record.unit_price
여기서 핵심은 store=True 입니다. 이것이 없으면 필드 값은 읽을 때마다 다시 계산되지만 데이터베이스에는 저장되지 않습니다.
UI 상에서는 저장된 계산 필드를 다른 필드와 구분할 수 없습니다. 폼, 리스트, 리포트 어디에서나 동일하게 보이며, 필터/그룹/내보내기 대상이 됩니다. 값이 계산으로 나온 것인지 사용자는 알 필요가 없습니다.
저장된 계산 필드 vs 비저장 계산 필
이 차이를 이해하는 것은 모든 Odoo 개발 작업의 기본입니다:
- 비저장 계산 필드: 읽을 때마다 즉석 계산. 필터·검색·그룹화 불가. 저장공간은 덜 쓰지만 DB 쿼리에서 사용할 수 없음.
- 저장된 계산 필드: 의존 필드가 바뀔 때 계산되어 DB에 저장됨. 검색·필터·내보내기 가능. 일반 컬럼처럼 데이터베이스 공간을 차지함.
어느 쪽이 더 낫냐는 상황에 달려 있습니다. 단순히 폼에 표시만 하면 된다면 비저장이 적절하고, 검색·정렬·집계를 하려면 저장된 필드를 선택해야 합니다.
필드의 동작 원리
Odoo에 저장된 계산 필드를 정의하면 ORM이 @api.depends()에 나열된 필드들을 바탕으로 자동 재계산 트리거를 설정합니다.
의존 필드 중 하나라도 변경되면 Odoo는 해당 레코드를 재계산 대상으로 표시하고, compute 메서드를 실행한 뒤 결과를 데이터베이스 컬럼에 씁니다.
재계산의 생명주기
과정은 단계별로 다음과 같습니다:
- 사용자나 자동 프로세스가
@api.depends()에 명시된 필드 중 하나를 변경한다. - Odoo가 변경을 감지하고 해당 의존성을 가진 모든 레코드를 식별한다.
- 식별된 레코드들에 대해 compute 메서드를 호출한다.
- 계산된 값이 데이터베이스 컬럼에 쓰인다.
- 이제 업데이트된 값으로 검색·필터·내보내기가 가능해진다.
대부분 경우 이 재계산은 같은 트랜잭션 안에서 즉시 일어납니다. 다만 대량 작업에서는 일부 재계산을 백그라운드로 연기할 수 있습니다.
연관 모델 간 의존성
@api.depends()는 관련 모델의 필드에 대해 도트(.) 표기법을 지원합니다. 예를 들면:
@api.depends('partner_id.country_id.name')
def _compute_country_name(self):
for record in self:
record.country_name = record.partner_id.country_id.name or ''
이 경우 Odoo는 partner_id, country_id, name의 변경을 모두 추적합니다. 파트너의 국가명이 바뀌면 관련된 모든 레코드가 자동으로 재계산됩니다. 이것이 Odoo 프레임워크의 강력한 점 중 하나입니다.
데이터베이스 영향
저장된 계산 필드는 PostgreSQL 테이블에 실제 컬럼을 생성합니다. 따라서 SQL 쿼리에서 직접 참여하며, 일반 필드와 동일하게 검색과 필터가 빠르고 효율적으로 동작합니다.
비즈니스 적용 사례
저장된 계산 필드는 Odoo의 여러 영역에서 실무에 바로 활용할 수 있습니다. 다음은 실제 업무에서 자주 쓰이는 다섯 가지 예입니다.
1. 영업: 주문행별 마진 비율
영업팀은 각 주문행의 마진 비율을 바로 보고 싶어 합니다. 저장된 계산 필드는 판매단가와 원가를 이용해 마진을 계산해 주문행에 저장합니다. 관리자는 마진 비율로 주문을 필터링하거나, 손익이 나쁜 항목을 즉시 찾고, 피벗에서 마진 구간별 그룹화를 할 수 있습니다.
2. CRM: 리드의 활동 공백일수
CRM 리드 모델에 저장된 계산 필드를 두어 마지막 활동 이후 며칠이 지났는지 추적할 수 있습니다. 매일 아침 예약된 액션으로 재계산을 트리거하면 영업팀은 비활성 임계치로 리드를 필터링해 후속조치를 자동화할 수 있습니다.
3. 재고: 순가용 수량
복잡한 재고 규칙이 있는 품목의 경우, 보유수량에서 예약수량을 뺀 값을 저장된 계산 필드에 미리 계산해 둘 수 있습니다. 이렇게 하면 제품 관리자들은 화면에서 모든 행마다 무거운 재고 계산을 하지 않아도 가용성으로 정렬·필터링할 수 있습니다.
4. 회계: 고객별 연체 송장 수
거래처 모델에 저장된 계산 필드로 현재 연체된 송장 수를 집계해 둘 수 있습니다. 회계팀은 연락처 목록을 열어 연체 송장 수로 고객을 정렬해 바로 우선순위를 정할 수 있습니다. 이런 편의는 값이 DB에 저장되어 있을 때만 가능합니다.
5. 생산: 총 예상 작업 시간
BOM에서 모든 작업센터 작업의 예상 소요시간을 합산해 저장된 계산 필드에 보관하면, 생산 계획자는 BOM을 총 작업시간으로 필터·정렬해 용량 계획과 일정 수립에 활용할 수 있습니다. 작업이 추가되거나 수정될 때마다 합계가 자동 갱신됩니다.
필드 생성 및 커스터마이징 방법
저장된 계산 필드를 만드는 방법은 크게 두 가지입니다. 간단한 경우는 Odoo Studio를, 복잡한 로직은 커스텀 파이썬 모듈을 사용합니다.
Odoo Studio 사용하기
Odoo Studio에서는 코드를 쓰지 않고도 계산 필드를 추가할 수 있습니다. Studio에서 Integer, Float, Monetary 타입 필드를 만들 때 수식 옵션을 활성화하면 파이썬과 유사한 표현식을 입력할 수 있고, Studio가 내부 의존성 추적을 처리합니다.
Studio 기반 계산 필드는 같은 레코드 내 필드들 간의 단순 산술식일 때 적합합니다. 설정이 쉽고 개발 환경이 필요 없지만, 관련 모델 필드 접근, 조건문, 자식 레코드 집계 같은 복잡한 로직은 Studio로 처리하기 어렵습니다. 그런 경우에는 커스텀 모듈이 필요합니다.
이 구분은 Odoo 커스터마이징을 계획할 때 중요합니다: 단순한 상황에는 Studio가 빠르지만, 로직이 복잡해지면 Python이 유연성에서 우위입니다.
커스텀 파이썬 모듈 사용하기
기본 공식을 넘어서는 모든 경우에는 커스텀 Odoo 모듈 안에서 파이썬으로 필드를 정의합니다. 예를 들어 판매 주문행에 마진 비율 필드를 추가하는 코드는 다음과 같습니다:
from odoo import models, fields, api
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
x_margin_pct = fields.Float(
string='Margin %',
compute='_compute_margin_pct',
store=True,
digits=(5, 2),
)
@api.depends('price_unit', 'purchase_price')
def _compute_margin_pct(self):
for line in self:
if line.price_unit:
line.x_margin_pct = (
(line.price_unit - line.purchase_price) / line.price_unit
) * 100
else:
line.x_margin_pct = 0.0
모듈을 설치하면 Odoo는 데이터베이스에 x_margin_pct 컬럼을 생성하고 기존 레코드에 대해 compute 메서드를 실행해 초기값을 채운 후 price_unit, purchase_price의 변경을 추적합니다.
커스텀 필드명 앞에 x_ 접두어를 붙이는 것은 핵심 필드와 충돌을 피하기 위한 관례입니다. 이는 Odoo 개발에서 표준적인 관행입니다.
저장된 계산 필드를 사용자가 수정 가능하게 만들기
기본적으로 계산 필드는 읽기 전용입니다. 사용자가 계산된 값을 수동으로 덮어쓸 수 있게 하려면 compute와 함께 inverse 메서드를 정의할 수 있습니다. 사용자가 값을 쓸 때 inverse가 호출되어 원천 필드들을 적절히 업데이트합니다. 이 패턴은 계산값을 기본값으로 쓰되 예외적으로 수동 조정이 필요할 때 유용합니다.
Odoo Studio 필드와 XML-RPC API
XML-RPC API로 Odoo의 표준 필드를 생성하려면 ir.model.fields 모델을 사용할 수 있습니다. 다만 저장된 계산 필드의 핵심 로직(컴퓨트 함수)은 서버 측 코드에 있어야 합니다. API 방식은 자동화된 배포에서 단순 필드를 프로비저닝할 때 유용하지만, 복잡한 계산 로직은 서버에 설치된 커스텀 모듈이 필요합니다.
모범 사례
다음은 경험 많은 Odoo 컨설턴트들이 저장된 계산 필드를 다룰 때 따르는 실무 원칙들입니다.
의존성은 정확하게 선언하라
@api.depends()에는 compute 메서드가 읽는 모든 필드를 빠짐없이 나열해야 합니다. 하나라도 빠뜨리면 그 의존성이 바뀔 때 필드가 갱신되지 않습니다. 메서드 내부를 줄 단위로 점검해 모든 필드 접근을 데코레이터에 포함시키세요.
계산 메서드는 빠르게 유지하라
계산 메서드는 의존성 변경에 영향을 받는 모든 레코드에 대해 실행됩니다. 바쁜 인스턴스에서는 수천 건이 한꺼번에 걸릴 수 있습니다. 가능하면 계산 메서드 안에서 추가 DB 쿼리를 피하고, 관련 데이터는 이미 로드된 필드를 활용하세요.
store=True는 필요한 경우에만 사용하라
저장 필드는 DB 공간을 소비하고 재계산마다 쓰기 작업을 유발합니다. 단순히 폼에 표시만 할 목적이라면 비저장 계산 필드가 더 가볍습니다. 무턱대고 모든 계산 필드를 저장형으로 만드는 것을 피하세요.
경계 상황을 계산 메서드에서 처리하라
계산 메서드 내부에서 빈 값, 누락된 관련 레코드, 0으로 나누기 같은 예외를 항상 처리하세요. 이러한 경우가 흔히 무시된 채로 남아 계산 필드에 잠복 오류를 만드는 원인이 됩니다. 명시적 체크와 안전한 기본값을 두는 것이 좋습니다.
대용량 테이블에서 초기 재계산을 계획하라
새 저장된 계산 필드를 추가하면 Odoo는 기존 모든 레코드에 대해 초기 재계산을 수행합니다. 수십만 건 테이블에서는 시간이 많이 소요될 수 있으니 스테이징에서 마이그레이션을 시험하고 운영 배포 시 다운타임 또는 백그라운드 처리 전략을 세우세요.
순환 의존성을 피하라
필드 A가 필드 B에 의존하고 B가 다시 A에 의존하면 모듈 로드시 오류가 발생합니다. 의존성은 단방향 흐름으로 설계하세요.
자주 빠지는 실수들
store=True를 깜빡하는 실수
가장 흔한 실수는 이것입니다. 폼에서는 필드가 잘 보이기 때문에 테스트 단계에서 문제가 드러나지 않습니다. 그러나 나중에 필터를 걸거나 리포트에 포함하려 할 때 동작하지 않게 됩니다. 미리 검색 가능성이 필요한지 결정하고 필요하다면 처음부터 store=True를 설정하세요.
@api.depends에 의존성을 빠뜨림
예컨대 compute 메서드에서 partner_id.country_id를 읽는데 데코레이터에 partner_id만 적어두면 국가가 바뀔 때 필드가 갱신되지 않습니다. 접근 경로의 각 단계마다 명시적으로 나열하세요.
계산 메서드의 무음 오류
계산 메서드가 특정 레코드에서 예외를 던지면 Odoo는 해당 레코드의 재계산을 조용히 건너뛰고 이전 저장값을 유지합니다. 예외는 서버 로그에만 남고 사용자에게는 표시되지 않아 오래된 값이 남는 원인이 됩니다. 다양한 입력 케이스로 충분히 테스트하세요.
대규모 데이터셋에서 성능 저하
개발 환경에서는 잘 돌아가던 계산 메서드가 실제 운영에서 수만 건 테이블로 확장되면 병목이 될 수 있습니다. 레코드당 트리거되는 DB 쿼리 수를 면밀히 점검하세요. 레코드당 추가 쿼리 하나가 수만 건과 곱해지면 큰 부하가 됩니다.
계산 메서드에서 sudo() 사용
권한을 무시하기 위해 계산 메서드에서 sudo()를 호출하는 것은 보안 리스크입니다. 계산값을 통해 현재 사용자가 볼 수 없어야 할 데이터가 노출될 수 있습니다. 보안 영향을 충분히 검토한 경우에만 제한적으로 사용하세요.
모든 상황에서 즉시 재계산되리라 기대하지 마라
대부분 인터랙티브 작업에서는 재계산이 동기적으로 일어나지만, 배치 임포트나 백그라운드 작업, 특정 컨텍스트 플래그가 설정된 ORM 동작에서는 재계산이 지연될 수 있습니다. 저장된 값이 항상 순간적으로 최신이라고 가정하지 말고, 해당 필드가 사용될 컨텍스트에서 동작을 확인하세요.
결론
저장된 계산 필드는 Odoo를 확장하거나 맞춤화할 때 매우 유용한 도구입니다. 계산을 자동화하고 데이터 일관성을 유지하며 사용자가 수동으로 신경 쓰지 않아도 검색·내보내기가 가능하도록 해줍니다.
기억해야 할 핵심 사항들:
- 검색·필터·내보내기가 필요하면
store=True를 사용하세요. - 교차 모델 경로를 포함해
@api.depends()에 모든 의존성을 빠짐없이 선언하세요. - 계산 메서드는 빠르게 유지하고 경계 상황을 명시적으로 처리하세요.
- 단순 계산식은 Odoo Studio로 빠르게 만들고, 복잡한 로직은 파이썬으로 구현하세요.
- 대용량 테이블에 배포할 때 초기 재계산을 반드시 계획하세요.
신규 모듈을 만들든 기존 모델을 확장하든, 또는 Odoo 필드 타입을 처음 살펴보든 저장된 계산 필드는 ORM, DB, 비즈니스 로직이 만나는 지점에 자리합니다. 그 작동 원리를 잘 이해하면 시스템 설계에 큰 도움이 됩니다.
Odoo 도입 지원이 필요하신가요?
Dasolo는 기업들이 다양한 비즈니스 요구에 맞춰 Odoo를 구현·커스터마이즈·최적화하도록 돕습니다. 계산 필드를 데이터 모델에 추가하거나 계산값 기반 리포트를 만들거나 Odoo 개발을 심화하고 싶다면 저희 팀이 경험으로 도와드릴 수 있습니다.
프로젝트 지원이 필요하면 연락 주세요. 사용 사례를 함께 검토해 비즈니스에 맞는 최적의 접근법을 찾아 드리겠습니다.