File size: 2,645 Bytes
bf8519f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# Topic: credit_notes β€” posted customer CREDIT NOTES (returns), at document grain.
#
# Why its own topic rather than a filtered measure on a shared one: a FILTERED measure
# (`store_filter_sql`) exists only on the store path, and `semantic.metric()` resolves a sum by
# handing the metric's `field` to `O.sum_field(entity, domain, field)` with no room for a
# per-measure filter. A metric that can be computed on one path and not the other cannot be
# parity-checked β€” and parity is the admission bar for filtering on it. Two topics with two
# DOMAINS give both paths the same scope by construction.
key: credit_notes
label: Customer credit notes (returns)
entity: account.move
domain_builder: credit_note_domain

scope:
  documents: "posted customer credit notes only (move_type = out_refund, state = posted)"
  channels: >-
    COMPANY-LEVEL. Credit notes carry team_id = 1 for everything, so a business-unit filter on
    the document is meaningless β€” modules/returns.py says the same and attributes by the
    CUSTOMER instead. A BU-scoped caller is refused rather than given a company number wearing
    their name.
  excluded: >-
    Nothing. modules/returns.py β€” the number the business already reads β€” does not exclude the
    GIFTWARE DEALS partner the wholesale sales topics do. Matching it keeps one definition of
    "returns"; the customer-grid pool is wholesale anyway, so the intersection makes the
    difference unobservable there.
  basis: "untaxed document amounts (amount_untaxed_signed, NEGATED β€” see the metric)"
  date_field: "invoice_date"

grain: "one row per posted credit note; time-filterable by invoice_date"

store:
  table: account_move
  alias: m
  join: "LEFT JOIN res_partner rp ON rp.id = m.partner_id"
  date_col: "m.invoice_date"
  scope_sql: "m.state = 'posted' AND m.move_type = 'out_refund'"
  dims:
    partner: {col: "m.partner_id", name_col: "m.partner_name", label: "Customer"}

ai_context: >
  Posted customer credit notes β€” refunds/returns. ⚠ `amount_untaxed_signed` is NEGATIVE on a
  credit note (verified on the tenant store 2026-07-27: all 3,006 posted refunds are negative,
  summing to -358,141.70), so the `returns` metric declares `negate: true` and reports a POSITIVE
  amount. A returns figure that comes out negative means the negation was lost, and it will read
  as "we have no returns" rather than as an error. This topic is COMPANY-LEVEL: credit notes are
  not business-unit tagged, so never present a returns number as belonging to Fisch or Royal.
  Odoo has no return-REASON field, so concentration (which customers, which SKUs) is the only
  diagnostic available.