Odoo の在庫モジュールを JSON-RPC で操作1

Python で実装されたオープンソース ERP である Odoo の在庫モジュールを JSON-RPC を使って操作してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20200112/

はじめに

Docker で PostgreSQL と Odoo を実行しておきます。

PostgreSQL 実行
$ docker run -d -e POSTGRES_USER=odoo -e POSTGRES_PASSWORD=odoo -e POSTGRES_DB=postgres --name db postgres
Odoo 実行
$ docker run -d -p 8069:8069 --name odoo --link db:db odoo

http://localhost:8069/ へアクセスすると DB の初期構築画面(以下)が表示されるので設定します。

f:id:fits:20200112193002p:plain

「Demo data」 にチェックを付けておくと、デモ用のデータが登録されるので、今回はこのデータを使います。

デフォルトの状態では在庫管理モジュールが導入されていないため、「アプリ」画面で「在庫」モジュールをインストールしておきます。

f:id:fits:20200112193044p:plain

Odoo には JSON-RPC と XML-RPC が用意されており、これらを利用することで永続モデルクラス(models ディレクトリに配置)等のメソッドを外部から呼び出せるようになっています。

今回は JSON-RPC の方を使いますが、参考までに XML-RPC の処理を Python で実装してみると以下のようになりました。

なお、qty_available は在庫モジュールをインストールする事によって Product クラス(product.product)へ追加される項目です。

sample.py (XML-RPC で商品情報を取得するサンプル)
import xmlrpc.client

url = 'http://localhost:8069'

db = 'odoo1'
user = 'admin@example.com'
password = 'pass'

common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common')

# 認証
uid = common.authenticate(db, user, password, {})

print(f'uid: {uid}')

models = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object')

obj = 'product.product'

# 商品の件数取得
count = models.execute(db, uid, password, obj, 'search_count', [])

print(f'count: {count}')

# 取得する項目
kw = {'fields': ['name', 'lst_price', 'product_tmpl_id', 'qty_available']}

# 商品情報取得
products = models.execute_kw(db, uid, password, obj, 'search_read', [], kw)

for p in products:
    print(p)
実行結果
> python sample.py

uid: 2
count: 32
{'id': 15, 'name': 'Customizable Desk (CONFIG)', 'lst_price': 800.4, 'product_tmpl_id': [9, 'Customizable Desk (CONFIG)'], 'qty_available': 60.0}
{'id': 16, 'name': 'Corner Desk Right Sit', 'lst_price': 147.0, 'product_tmpl_id': [10, '[E-COM06] Corner Desk Right Sit'], 'qty_available': 45.0}
{'id': 17, 'name': 'Large Cabinet', 'lst_price': 320.0, 'product_tmpl_id': [11, '[E-COM07] Large Cabinet'], 'qty_available': 250.0}
・・・

在庫管理

それでは、JSON-RPC で在庫の操作を行ってみます。

JSON-RPC や XML-RPC で任意の処理を呼び出すためには、認証を行って uid を取得します。

JSON-RPC における認証は、下記のような JSON/jsonrpc へ POST する事で実施します。

args には DB の初期構築時に設定した Database NameEmailPassword の値を順に指定した後、最後の引数を {}null 等にしておきます。

id はリクエストとレスポンスを識別するためのもので、任意の文字列や数値を指定する事ができるようです。

認証用リクエスJSON
{
  "jsonrpc": "2.0",
  "method": "call",
  "id": "a1",
  "params": {
    "service": "common",
    "method": "authenticate",
    "args": ["odoo1", "admin@example.com", "pass", {}]
  }
}

認証に成功すると以下のようなレスポンスが返り、result 項目の値が uid となっています。

id はリクエストで指定したものと同じ値になります。

認証用レスポンス JSON
{"jsonrpc": "2.0", "id": "a1", "result": 2}

認証で取得した uid を使ってモデルクラスのメソッドを呼び出すには、以下のような JSON を使います。(execute_kw の場合は args 内の要素が 1つ増える)

任意のメソッド呼び出し用リクエスJSON(execute の場合)
{
    "jsonrpc": "2.0",
    "method": "call",
    "id": <任意の値>,
    "params": {
        "service": "object",
        "method": "execute",
        "args": [<Database Name>, <uid>, <Password>, <モデル名>, <メソッド名>, <メソッドの引数>]
    }
}

(a) 在庫数の取得

まずは、指定商品の在庫数を取得してみます。

商品の在庫数は、在庫モジュールによって Product クラスへ追加された qty_availablevirtual_available で取得できるようです。

qty_available が商品の在庫画面上で 手持在庫 として表示される実際の在庫数、virtual_available が画面上の 見通し として表示される数値で実際の在庫数に入出荷の予定を加味したものです。( virtual_available = qty_available + incoming_qty - outgoing_qty

sample1.js
const axios = require('axios')

const url = 'http://localhost:8069/jsonrpc'

const db = 'odoo1'
const user = 'admin@example.com'
const password = 'pass'

const productId = parseInt(process.argv[2])

const main = async () => {
    // 認証
    const auth = await axios.post(url, {
        jsonrpc: '2.0',
        method: 'call',
        id: 'a1',
        params: {
            service: 'common',
            method: 'authenticate',
            args: [db, user, password, {}]
        }
    })

    const uid = auth.data.result

    console.log(`uid: ${uid}`)
    // 商品情報の取得
    const prd = await axios.post(url, {
        jsonrpc: '2.0',
        method: 'call',
        id: 'a2',
        params: {
            service: 'object',
            method: 'execute_kw',
            args: [
                db, 
                uid, 
                password, 
                'product.product', 
                'read', 
                [ productId ],
                { fields: ['name', 'product_tmpl_id', 'qty_available', 'virtual_available'] }
            ]
        }
    })

    console.log(prd.data.result)
}

main().catch(err => console.error(err))

実行結果は以下の通りです。

実行結果1
> node sample1.js 19

uid: 2
[
  {
    id: 19,
    name: 'Large Desk',
    product_tmpl_id: [ 13, '[E-COM09] Large Desk' ],
    qty_available: 0,
    virtual_available: 0
  }
]
実行結果2
> node sample1.js 21

・・・
[
  {
    id: 21,
    name: 'Cabinet with Doors',
    product_tmpl_id: [ 15, '[E-COM11] Cabinet with Doors' ],
    qty_available: 8,
    virtual_available: 128
  }
]

(b) 在庫数の調整

次に、指定した商品の在庫数を更新してみます。

Odoo の在庫モジュールでは、在庫ロケーション(stock.location)間の在庫移動(stock.move)として在庫数の変動を表現しているようです。

調整用途で在庫数を更新する際にも調整用ロケーション Virtual Locations/YourCompany: Inventory adjustment から在庫ロケーション WH/Stock 等への在庫移動という形で処理する事になります。

ここでは、商品の在庫数を簡単に調整するためのウィザード処理 ※ として用意されている(と思われる) ProductChangeQuantitystock.change.product.qty) を使って在庫数を更新してみます。

 ※ 在庫モジュールの wizard ディレクトリにソースが配置されている

それには、stock.change.product.qtycreate として change_product_qty を呼び出せば良さそうです。

create の際に product_idproduct_tmpl_id で商品を、new_quantity で更新後の在庫数を指定すれば、stock.warehouselot_stock_id から在庫のロケーションを特定して在庫移動が作成されるようです。

sample2.js
const axios = require('axios')

・・・

const productId = parseInt(process.argv[2])
const productTmplId = parseInt(process.argv[3])
const qty = parseInt(process.argv[4])

const main = async () => {
    const auth = ・・・

    const uid = auth.data.result

    // 作成
    const chg = await axios.post(url, {
        jsonrpc: '2.0',
        method: 'call',
        id: 'b2',
        params: {
            service: 'object',
            method: 'execute',
            args: [db, uid, password, 'stock.change.product.qty', 'create', {
                product_id: productId,
                product_tmpl_id: productTmplId,
                new_quantity: qty
            }]
        }
    })

    const sid = chg.data.result

    console.log(`create id: ${sid}`)

    // 在庫数の更新
    const res = await axios.post(url, {
        jsonrpc: '2.0',
        method: 'call',
        id: 'b3',
        params: {
            service: 'object',
            method: 'execute',
            args: [db, uid, password, 'stock.change.product.qty', 'change_product_qty', sid]
        }
    })

    console.log(res.data.result)
}

main().catch(err => console.error(err))

上記を実行して、(a) で在庫数が 0 だった Large Desk(product_id = 19, product_tmpl_id = 13) の在庫数を 100 にしてみます。

実行結果
> node sample2.js 19 13 100

create id: 6
{ type: 'ir.actions.act_window_close' }

商品の在庫数を確認してみると 100 に変化しており、処理は成功しているようです。

在庫数の確認
> node sample1.js 19

・・・
[
  {
    id: 19,
    name: 'Large Desk',
    product_tmpl_id: [ 13, '[E-COM09] Large Desk' ],
    qty_available: 100,
    virtual_available: 100
  }
]

DB の stock_move テーブルを確認してみると下記のレコードが追加されていました。

stock_move テーブルへ追加されたレコード(を JSON 化したもの)
{
    "id" : 44,
    "name" : "Product Quantity Updated",
    ・・・
    "company_id" : 1,
    ・・・
    "product_id" : 19,
    "description_picking" : null,
    "product_qty" : 100.0,
    "product_uom_qty" : 100.000,
    "product_uom" : 1,
    "location_id" : 14,
    "location_dest_id" : 8,
    ・・・
    "state" : "done",
    ・・・
    "procure_method" : "make_to_stock",
    ・・・
    "create_uid" : 2,
    ・・・
}

在庫ロケーション 14(Virtual Locations/YourCompany: Inventory adjustment)から 8(WH/Stock)への在庫移動として処理されています。

今回はここまで、次回は顧客への出荷を処理してみたいと思います。