0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

odooモデルのデータをADODB.Recordsetに抽出し、Excelシートに表示する

0
Posted at

はじめに

odooには、External APIが備わっており、Excel等のVBAを使って、odooモデルのデータにアクセスすることができます。

External API は、External JSON-2 API に移行されます(2028年秋のodoo22.0)
現:External API
https://www.odoo.com/documentation/19.0/developer/reference/external_api.html
新:External JSON-2 API
https://www.odoo.com/documentation/19.0/developer/reference/external_api.html

External APIを使うには、XMLやJSONの知識が必要ですが、odoo-json-rpc-vba を使えば、直接的にADODB.Recordsetにデータを抽出できます。
これは、VBA開発者にとって朗報です。

odoo-json-rpc-vba 付属のサンプルコード

odoo-json-rpc-vba には、サンプルコードが付属していますので、odoo開発環境を用意すれば、すぐに試すことができます。
尚、サンプルコードの詳細については、AIに聞いてみてください。

付属のサンプルコード
付属のサンプルコード
Public Sub DoExampleOdx()
    Dim oModelView As OdxModelView
    Dim oDomain As OdFilterDomain
    Dim oNames As OdFilterDomain
    
    Dim oClient As OdClient
    Dim oCtx As OdxContext
    
    Dim wb As Workbook
    Dim sht As Worksheet
    Dim rng As Range
    
    Dim colNameList As New Collection
    Dim colTagList As New Collection
    Dim v As Variant
    Dim s As String
    
    Set oClient = GetAuthConn()
    Set oCtx = NewContext(oClient)
    Set wb = Workbooks.Add
    Set sht = wb.Sheets(1)
    SetWorksheetName sht, "OdxModelView Example"
    Set rng = sht.Range("A1")
    
    ' target name
    s = "Mitchell"
    colTagList.Add s
    colNameList.Add "Mitchell Admin", s
    s = "Marc"
    colTagList.Add s
    colNameList.Add "Marc Demo", s
    s = "Gemini"
    colTagList.Add s
    colNameList.Add "Gemini Furniture", s
            
    
    DebugRange rng, "--------------"
    DebugRange rng, " DoExampleOdx"
    DebugRange rng, "--------------"
    
    ' ==================
    '  New OdxModelView
    ' ==================
    Set oModelView = oCtx.NewModelView("res.partner")
    With oModelView
        ' -------------
        '  res.partner
        ' -------------
        DebugRange rng, .ModelName
        DebugRangeSchema rng, .ModelName, .GetModelSchema()
        
        .AddField "name"
        .AddField "is_company"
        .AddField "is_public"
        
        With .AddField("company_id")    ' many2one
            ' --------------------------
            '  res.company
            ' --------------------------
            DebugRange rng, "company_id: many2one --> " & .ModelName
            Debug.Assert .IsMany2One
            DebugRangeSchema rng, .ModelName, .GetModelSchema()
            
            .AddField "name"
            .AddField "city"
            
            With .AddField("currency_id")   ' many2one
                ' --------------------------
                '  res.currency
                ' --------------------------
                DebugRange rng, "company_id: many2one --> currency_id: many2one --> " & .ModelName
                Debug.Assert .IsMany2One
                DebugRangeSchema rng, .ModelName, .GetModelSchema()
                
                .AddField "name"
                .AddField "symbol"

            End With
            
            .AddField "layout_background"

        End With
        
        With .AddField("child_ids")     ' one2many
            ' --------------------------
            '  res.partner
            ' --------------------------
            DebugRange rng, "child_ids: one2many --> " & .ModelName
            Debug.Assert .IsOne2Many
            DebugRangeSchema rng, .ModelName, .GetModelSchema()
            
            .AddField "name"
        End With
                
        With .AddField("category_id")   ' many2many
            ' --------------------------
            '  res.partner.category
            ' --------------------------
            DebugRange rng, "category_id: many2many --> " & .ModelName
            Debug.Assert .IsMany2Many
            DebugRangeSchema rng, .ModelName, .GetModelSchema()
            
            .AddField "name"
        End With
    End With

    ' ==================
    '  Fetch data
    ' ==================
    Set oNames = NewDomain
    For Each v In colNameList
        oNames.AddArity v
    Next v
    Set oDomain = NewDomain
    oDomain.AddArity NewField("name").IsIn(oNames)
    oModelView.ExecuteSearchRead oDomain
    
    ' ==================
    '  All
    ' ==================
    oModelView.ClearFilter  ' Unfiltered
    With oModelView
        DebugPrintModelView wb, .RefMe
        With .GetRelatedModelView("company_id", True)
            DebugPrintModelView wb, .RefMe
            With .GetRelatedModelView("currency_id", True)
                DebugPrintModelView wb, .RefMe
            End With
        End With
        With .GetRelatedModelView("child_ids", True)
            DebugPrintModelView wb, .RefMe
        End With
        With .GetRelatedModelView("category_id", True)
            DebugPrintModelView wb, .RefMe
        End With
    End With
    
    
    ' ==================
    '  Filtered
    ' ==================
    For Each v In colTagList
        s = colNameList(CStr(v))
        
        oModelView.SetFilter "name = '" & s & "'"   ' Filtered
        With oModelView
            DebugPrintModelView wb, .RefMe, CStr(v)
            With .GetRelatedModelView("company_id")
                DebugPrintModelView wb, .RefMe, v & "(company_id)"
                With .GetRelatedModelView("currency_id")
                    DebugPrintModelView wb, .RefMe, v & "(currency_id)"
                End With
            End With
            With .GetRelatedModelView("child_ids")
                DebugPrintModelView wb, .RefMe, v & "(child_ids)"
            End With
            With .GetRelatedModelView("category_id")
                DebugPrintModelView wb, .RefMe, v & "(category_id)"
            End With
        End With
    Next v
    
    sht.Select
    wb.Saved = True
    wb.Activate
End Sub

AIの要約
OdxModelView は「モデル → フィールド → 関連モデル → domain → search_read → Excel 出力」を一括で扱うための VBA ORM。
DoExampleOdx はその全機能を網羅したサンプル。

実用サンプル(販売アプリ)

実用的なサンプルとして、販売アプリ(sale.order)を中心として、関連モデル(partner / order_line / product / taxes)をたどり、Excelシートに抽出・表示してみます。

モデル:sale.order

  • partner_id(顧客)
  • order_line(明細行-複数)
    • product_id(商品)
    • product_template_attribute_value_ids(商品バリアントの属性値セット)
販売アプリ(sale.order)の実用サンプル

Public Sub DoExampleOdx_SaleOrder()

    Dim oModelView As OdxModelView
    Dim oDomain As OdFilterDomain
    Dim oClient As OdClient
    Dim oCtx As OdxContext
    
    Dim wb As Workbook
    Dim sht As Worksheet
    Dim rng As Range

    ' ==================
    '  Setup
    ' ==================
    Set oClient = GetAuthConn()
    Set oCtx = NewContext(oClient)
    
    Set wb = Workbooks.Add
    Set sht = wb.Sheets(1)
    SetWorksheetName sht, "SaleOrder ModelView"
    Set rng = sht.Range("A1")

    DebugRange rng, "--------------"
    DebugRange rng, " DoExampleOdx_SaleOrder"
    DebugRange rng, "--------------"

    ' ==================
    '  sale.order ModelView
    ' ==================
    Set oModelView = oCtx.NewModelView("sale.order")

    With oModelView

        DebugRange rng, .ModelName
        DebugRangeSchema rng, .ModelName, .GetModelSchema()

        ' ---- Basic fields ----
        .AddField "name"            ' SO number
        .AddField "date_order"
        .AddField "state"
        .AddField "amount_total"
        .AddField "currency_id"

        ' ---- Many2one: partner_id (customer) ----
        With .AddField("partner_id")
            DebugRange rng, "partner_id → " & .ModelName
            Debug.Assert .IsMany2One
            DebugRangeSchema rng, .ModelName, .GetModelSchema()

            .AddField "name"
            .AddField "email"
            .AddField "phone"
        End With

        ' ---- One2many: order_line ----
        With .AddField("order_line")
            DebugRange rng, "order_line → " & .ModelName
            Debug.Assert .IsOne2Many
            DebugRangeSchema rng, .ModelName, .GetModelSchema()

            .AddField "name"
            .AddField "product_uom_qty"
            .AddField "price_unit"
            .AddField "price_subtotal"
            .AddField "currency_id"

            ' ---- Many2one: product_id ----
            With .AddField("product_id")
                DebugRange rng, "order_line.product_id → " & .ModelName
                Debug.Assert .IsMany2One
                DebugRangeSchema rng, .ModelName, .GetModelSchema()

                .AddField "name"
                .AddField "default_code"
                .AddField "list_price"
            End With

            ' ---- Many2many: product_template_attribute_value_ids ----
            With .AddField("product_template_attribute_value_ids")
                DebugRange rng, "order_line.product_template_attribute_value_ids → " & .ModelName
                Debug.Assert .IsMany2Many
                DebugRangeSchema rng, .ModelName, .GetModelSchema()

                .AddField "name"
                .AddField "product_attribute_value_id"
                .AddField "attribute_line_id"
                .AddField "price_extra"
                .AddField "currency_id"
            End With
            
        End With

    End With

    ' ==================
    '  Domain: Confirmed orders only
    ' ==================
    Set oDomain = NewDomain
    oDomain.AddArity NewField("state").Eq("sale")
    oModelView.ExecuteSearchRead oDomain

    ' ==================
    '  Output: main + related models
    ' ==================
    With oModelView
        DebugPrintModelView wb, .RefMe, "sale.order"

        With .GetRelatedModelView("partner_id", True)
            DebugPrintModelView wb, .RefMe, "partner_id"
        End With

        With .GetRelatedModelView("order_line", True)
            DebugPrintModelView wb, .RefMe, "order_line"

            With .GetRelatedModelView("product_id", True)
                DebugPrintModelView wb, .RefMe, "product_id"
            End With

            With .GetRelatedModelView("product_template_attribute_value_ids", True)
                DebugPrintModelView wb, .RefMe, "value_ids"
            End With

        End With
    End With
    
    ' ==================
    '  Output: filtered
    ' ==================
    oModelView.SetFilter "name = 'S00007'"   ' filter by SO number
    With oModelView
        DebugPrintModelView wb, .RefMe, "sale.order"

        With .GetRelatedModelView("partner_id")
            DebugPrintModelView wb, .RefMe, "partner_id"
        End With

        With .GetRelatedModelView("order_line")
            DebugPrintModelView wb, .RefMe, "order_line"
            
            ' "order_line" records
            Do Until .Recordset.EOF
                
                With .GetRelatedModelView("product_id")
                    DebugPrintModelView wb, .RefMe, "product_id"
                End With
    
                With .GetRelatedModelView("product_template_attribute_value_ids")
                    DebugPrintModelView wb, .RefMe, "value_ids"
                End With
                
                .Recordset.MoveNext
            Loop
        End With
    End With

    sht.Select
    wb.Saved = True
    wb.Activate

End Sub

明細行のアクセスについて

1件の sale.order に対して、複数の order_line がありますので、 order_lineの Recordset のカーソルを移動させながら、 product_id と product_template_attribute_value_ids とをたどり、抽出・表示しています。
そのため、このサンプルコードでは、明細行の1件毎にシートを追加しています。

カーソル移動
SetFilerや GetRelatedModelView により、内包されている ADODB.Recordset の Filter がセットされ、カーソルは、1件目のレコードセットに移動されます。
EOF が真になるまで、 MoveNext でカーソルを移動します。

つまり

odooのモデルでは、一般的な外部キー(FK:Foreign Key)である、many2one項目に加えて、その逆のone2manyやmany2manyを備えているため、 sale.order から order_line をたどることができます。
そして、このツールでは、親レコードの値(現在行)を使って、関連するRecordsetを絞り込んでいます。

  • SetFilter
    → ModelView が持つ Recordset 自体を絞り込む

  • GetRelatedModelView
    → 親レコードの値を使って、関連 ModelView の Recordset を
    Filter で絞り込んで生成する

  • 親の SetFilter は子には継承されない。
    子は 親の現在行に基づく Filter だけで絞られる

おわりに

  • odoo-json-rpc-vba を使うと、VBA から Odoo のモデルにアクセスし、
    データを ADODB.Recordset として直接扱うことができます

  • OdxModelView は、
    モデル定義 → フィールド追加 → 関連モデルの展開 → domain 指定 → search_read → Excel 出力 を一括で扱える仕組みになっています

  • SetFilter は、ModelView が保持している Recordset 全体を絞り込むために使われます。

  • GetRelatedModelView は、親レコード(現在行)の値をもとに、
    関連する ModelView の Recordset を Filter を使って絞り込んで生成します

  • 親の SetFilter は子には継承されません
    子の ModelView は、親の現在行に基づく 独自の Filter によって絞り込まれます

  • 販売アプリの例では、
    sale.order
    → partner
    → order_line
    → → product
    → → attributes
    と関連をたどりながら、Excel に 階層的に 出力しています

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?