inv/odoo_invoice_generator.py
2026-01-31 22:18:30 +09:00

313 lines
9.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Odoo請求書発行システムREST APIを使用
========================================
"""
import requests
from datetime import datetime
import json
import logging
class OdooAPI:
"""
Odoo APIクライアント
"""
def __init__(self, base_url: str, db: str, username: str, password: str):
self.base_url = f"{base_url}/api/v13"
self.db = db
self.session = requests.Session()
self.login()
def login(self) -> bool:
"""
Odoo APIに認証する
戻り値:
bool: 認証成功の場合はTrue、失敗の場合はFalse
"""
url = f"{self.base_url}/login/db_{self.db}"
data = {
"jsonrpc": "2.0",
"method": "call",
"params": {
"service": "object",
"method": "service_login",
"args": [self.db, username, password]
}
}
response = self.session.post(url, json=data)
if response.status_code == 200:
result = response.json()
self.session_id = result["result"]["session_id"]
return True
else:
logging.error(f"ログイン失敗: {response.text}")
return False
def logout(self) -> bool:
"""
Odoo APIからログアウトする
戻り値:
bool: ログアウト成功の場合はTrue、失敗の場合はFalse
"""
url = f"{self.base_url}/login/logout"
data = {
"jsonrpc": "2.0",
"method": "call",
"params": {
"service": "object",
"method": "service_logout",
"args": [self.session_id]
}
}
response = self.session.post(url, json=data)
if response.status_code == 200:
return True
else:
logging.error(f"ログアウト失敗: {response.text}")
return False
def get_partner(self, partner_id: int) -> dict | None:
"""
顧客情報を取得する
引数:
partner_id (int): 取得する顧客のID
戻り値:
dict | None: 顧客データが見つかった場合、それ以外の場合はNone
"""
url = f"{self.base_url}/res.partner/{partner_id}"
headers = {
"Authorization": f"Session {self.session_id}",
"Content-Type": "application/json"
}
response = self.session.get(url, headers=headers)
if response.status_code == 200:
return response.json()["result"]
else:
logging.error(f"顧客情報取得失敗: {response.text}")
return None
def get_product(self, product_id: int) -> dict | None:
"""
商品情報を取得する
引数:
product_id (int): 取得する商品のID
戻り値:
dict | None: 商品データが見つかった場合、それ以外の場合はNone
"""
url = f"{self.base_url}/product.product/{product_id}"
headers = {
"Authorization": f"Session {self.session_id}",
"Content-Type": "application/json"
}
response = self.session.get(url, headers=headers)
if response.status_code == 200:
return response.json()["result"]
else:
logging.error(f"商品情報取得失敗: {response.text}")
return None
def create_invoice(self, invoice_data: dict) -> dict | None:
"""
新しい請求書を作成する
引数:
invoice_data (dict): 請求書データ
戻り値:
dict | None: 作成成功した場合の請求書データ、失敗の場合はNone
"""
url = f"{self.base_url}/account.move"
headers = {
"Authorization": f"Session {self.session_id}",
"Content-Type": "application/json"
}
response = self.session.post(url, headers=headers, json=invoice_data)
if response.status_code == 200:
return response.json()["result"]
else:
logging.error(f"請求書作成失敗: {response.text}")
return None
def get_invoices(self) -> list[dict]:
"""
全ての請求書を取得する
戻り値:
list[dict]: 請求書リスト
"""
url = f"{self.base_url}/account.move"
headers = {
"Authorization": f"Session {self.session_id}",
"Content-Type": "application/json"
}
response = self.session.get(url, headers=headers)
if response.status_code == 200:
return response.json()["result"]
else:
logging.error(f"請求書取得失敗: {response.text}")
return []
class InvoiceGenerator:
"""
Odoo請求書生成器
"""
def __init__(self, odoo_api: OdooAPI):
self.odoo_api = odoo_api
def generate_invoice(self, partner_id: int, product_id: int, quantity: int) -> dict | None:
"""
指定された顧客と商品に対して新しい請求書を生成する
引数:
partner_id (int): 顧客ID
product_id (int): 商品ID
quantity (int): 売上数量
戻り値:
dict | None: 作成成功した場合の請求書データ、失敗の場合はNone
"""
# 顧客情報を取得
partner = self.odoo_api.get_partner(partner_id)
if not partner:
logging.error(f"顧客 {partner_id} が見つからない")
return None
# 商品情報を取得
product = self.odoo_api.get_product(product_id)
if not product:
logging.error(f"商品 {product_id} が見つからない")
return None
# 合計金額を計算
price_unit = product["lst_price"]
subtotal = quantity * price_unit
tax_rate = 0.08 # 8%の消費税(例、必要に応じて変更可能)
tax_amount = subtotal * tax_rate / (1 + tax_rate)
total = subtotal + tax_amount
# 請求書データを作成
invoice_data = {
"journal_id": 1, # デフォルトの勘定科目ID必要に応じて変更可能
"partner_id": partner["id"],
"date_invoice": datetime.now().strftime("%Y-%m-%d"),
"move_type": "out_invoice",
"state": "draft", # 初期状態
"name": f"{partner['name']} への請求書",
"reference": f"INV-{datetime.now().strftime('%Y%m%d%H%M%S')}",
"user_id": 1, # デフォルトのユーザーID必要に応じて変更可能
"company_id": 1, # デフォルトの会社ID必要に応じて変更可能
"invoice_line_ids": [
{
"product_id": product["id"],
"name": f"{product['name']} x{quantity}",
"sequence": 10,
"type": "line",
"quantity": quantity,
"price_unit": price_unit,
"account_id": product.get("property_account_exp", {}).get("account_id"),
"analytic_index_ids": [],
"discount": 0.00
}
],
# 追加の行、割引などがあればここに追加可能
}
return self.odoo_api.create_invoice(invoice_data)
def generate_invoices(self, partner_ids: list[int], product_ids: list[int]) -> dict:
"""
複数の顧客と商品に対して請求書を生成する
引数:
partner_ids (list[int]): 顧客IDリスト
product_ids (list[int]): 商品IDリスト
戻り値:
dict: 請求書データとエラーメッセージを含む辞書
"""
# 結果辞書を初期化
result = {"invoices": [], "errors": []}
for partner_id in partner_ids:
for product_id in product_ids:
try:
invoice = self.generate_invoice(partner_id, product_id, 1)
if invoice:
result["invoices"].append(invoice)
except Exception as e:
result["errors"].append(f"請求書生成失敗: {str(e)}")
return result
# 実行例
if __name__ == "__main__":
# Odoo API接続パラメータを設定
base_url = "http://localhost:8069"
db_name = "mydatabase"
username = "admin"
password = "password123"
odoo_api = OdooAPI(base_url, db_name, username, password)
if not odoo_api.login():
print("Odoo APIへの認証失敗")
exit(1)
# インスタンスを作成
invoice_generator = InvoiceGenerator(odoo_api)
# 例1: 単一の請求書を生成
partner_id = 2
product_id = 3
quantity = 5
invoice = invoice_generator.generate_invoice(partner_id, product_id, quantity)
if invoice:
print("請求書作成成功:")
print(json.dumps(invoice, indent=4))
else:
print(f"顧客 {partner_id} と商品 {product_id} に対する請求書作成失敗")
# 例2: 複数の請求書を生成
partner_ids = [1, 2, 3]
product_ids = [101, 102, 103]
result = invoice_generator.generate_invoices(partner_ids, product_ids)
print("\n請求書生成結果:")
print(f"作成された請求書: {len(result['invoices'])}")
print(f"エラー: {len(result['errors'])}")
# 請求書を表示
for i, invoice in enumerate(result["invoices"]):
print(f"\n請求書 {i+1}:")
print(json.dumps(invoice, indent=4))
# エラーを表示
if result["errors"]:
print("\nエラーログ:")
for error in result["errors"]:
print(error)
# Odoo APIからログアウト
odoo_api.logout()