inv/claude/README.md
2026-01-31 22:18:30 +09:00

11 KiB
Raw Permalink Blame History

モバイルPOS・見積/納品/請求/領収書システム

概要

Proxmox CT上で動作するオフラインファーストのスマートフォンアプリケーション。 営業現場で完全スタンドアロンで見積・納品・請求・領収書を作成・管理し、 ネットワーク接続時にOdooと同期します。

アーキテクチャ

┌─────────────────┐
│  Android App    │ ← スマホ(完全オフライン対応)
│  SQLite DB      │
│  PDF生成        │
└────────┬────────┘
         │ (ネットワーク接続時)
         │
    ┌────▼─────────────────────────┐
    │  REST API コンテナ            │
    │  (Python FastAPI)             │
    │  - 同期エンドポイント          │
    │  - Odoo連携                   │
    └────┬────────────────┬─────────┘
         │                │
    ┌────▼──────┐  ┌─────▼─────┐
    │ PostgreSQL│  │   Odoo    │
    │   DB      │  │ (Sales)   │
    └───────────┘  └───────────┘

ディレクトリ構成

project_root/
├── docker-compose.yml          # Odoo + API + DB
├── api/
│   ├── main.py                 # FastAPI メイン
│   ├── requirements.txt
│   └── Dockerfile
├── addons/                      # Odooカスタムモジュール
├── scheduler/                   # 定期同期スクリプト
└── android/
    ├── Models.kt               # データモデル
    ├── DAO.kt                  # Room DAO
    ├── Database.kt             # Room DB
    ├── ApiService.kt           # Retrofit API
    └── PdfGenerator.kt         # PDF生成

セットアップ手順

1. Docker環境構築Proxmox CT

# リポジトリクローン
git clone <your-repo-url>
cd project_root

# 環境変数設定
cp .env.example .env
# .envを編集API_SECRET_KEY等

# コンテナ起動
docker-compose up -d

# ログ確認
docker-compose logs -f api
docker-compose logs -f odoo

2. Odooの初期セットアップ

# Odooにアクセス
# http://localhost:8069

# 以下のモジュールを有効化
# - Sales (見積・受注管理)
# - Invoicing (請求・領収書)
# - Accounting (会計・売掛金)

# API認証設定
# Admin > 設定 > API キーを生成

3. REST APIの初期化

# DB テーブル作成
docker-compose exec api python -c "from main import Base, engine; Base.metadata.create_all(bind=engine)"

# テスト
curl -X GET http://localhost:8000/api/v1/health \
  -H "X-API-Key: your_secret_key"

4. Androidアプリ開発

依存パッケージ (build.gradle)

dependencies {
    // Room
    implementation "androidx.room:room-runtime:2.6.0"
    implementation "androidx.room:room-ktx:2.6.0"
    kapt "androidx.room:room-compiler:2.6.0"
    
    // Retrofit
    implementation "com.squareup.retrofit2:retrofit:2.9.0"
    implementation "com.squareup.retrofit2:converter-gson:2.9.0"
    implementation "com.squareup.okhttp3:logging-interceptor:4.11.0"
    
    // Coroutines
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
    
    // Jetpack Compose
    implementation "androidx.compose.ui:ui:1.6.0"
    implementation "androidx.compose.material3:material3:1.1.0"
    
    // PDF (iText or Apache POI)
    implementation "com.itextpdf:itext-core:8.0.0"
    
    // Gson
    implementation "com.google.code.gson:gson:2.10.1"
}

AndroidManifest.xml の設定

<!-- パーミッション -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

build.gradle 設定

android {
    compileSdk 34
    
    defaultConfig {
        applicationId "com.example.mobilepos"
        minSdk 26
        targetSdk 34
    }
    
    buildFeatures {
        compose = true
    }
    
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.0"
    }
}

API エンドポイント

同期

POST /api/v1/sync

curl -X POST http://localhost:8000/api/v1/sync \
  -H "X-API-Key: secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "device_001",
    "last_sync_timestamp": null,
    "documents": [
      {
        "doc_type": "quotation",
        "customer_id": 1,
        "document_date": "2026-01-31T10:00:00",
        "items": [
          {
            "product_name": "商品A",
            "quantity": 10,
            "unit_price": 1000,
            "subtotal": 10000
          }
        ],
        "subtotal": 10000,
        "tax": 1000,
        "total": 11000,
        "payment_terms": {
          "billing_date": "2026-01-31",
          "payment_due_date": "2026-02-28",
          "payment_method": "bank_transfer"
        }
      }
    ]
  }'

顧客一覧

GET /api/v1/customers

curl http://localhost:8000/api/v1/customers \
  -H "X-API-Key: secret_key"

ドキュメント取得

GET /api/v1/documents/{id}

領収書自動生成

POST /api/v1/receipts/{invoice_id}

# 入金から1週間以内の請求書から領収書を生成
curl -X POST http://localhost:8000/api/v1/receipts/1 \
  -H "X-API-Key: secret_key"

Android実装ガイド

1. データベース初期化

val db = AppDatabase.getInstance(context)
val documentDao = db.documentDao()
val customerDao = db.customerDao()

2. ドキュメント作成・保存

val document = DocumentEntity(
    docType = "quotation",
    customerId = 1,
    documentDate = System.currentTimeMillis(),
    items = Gson().toJson(listOf(
        DocumentItemDto("商品A", 10.0, 1000.0, 10000.0)
    )),
    subtotal = 10000.0,
    tax = 1000.0,
    total = 11000.0,
    paymentDueDate = calculateDueDate(PaymentPattern.END_OF_MONTH),
    synced = false
)

documentDao.insertDocument(document)

3. PDF生成

val file = PdfGenerator.generateQuotationPdf(
    context = context,
    document = document,
    customer = customer,
    fileName = "quotation_${document.id}.pdf"
)

// ファイル共有
val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
val shareIntent = Intent(Intent.ACTION_SEND).apply {
    type = "application/pdf"
    putExtra(Intent.EXTRA_STREAM, uri)
}
startActivity(Intent.createChooser(shareIntent, "PDFを共有"))

4. 同期処理

suspend fun syncDocuments(context: Context) {
    val apiKey = "your_secret_key"
    val baseUrl = "http://your_api_server:8000"
    val apiService = ApiClient.getSyncApiService(baseUrl)
    
    val db = AppDatabase.getInstance(context)
    val unsyncedDocs = db.documentDao().getUnsyncedDocuments()
    
    val request = SyncRequestDto(
        deviceId = getDeviceId(),
        documents = unsyncedDocs.map { convertToDto(it) }
    )
    
    try {
        val response = apiService.syncDocuments(apiKey, request)
        if (response.isSuccessful && response.body()?.status == "success") {
            response.body()?.newDocuments?.forEach { doc ->
                db.documentDao().markDocumentAsSynced(doc["local_id"] as Int, System.currentTimeMillis())
            }
        }
    } catch (e: Exception) {
        Log.e("Sync", "Error: ${e.message}")
    }
}

5. 支払期限の計算

fun calculatePaymentDueDate(billingDate: Long, pattern: PaymentPattern): Long {
    val calendar = Calendar.getInstance().apply {
        timeInMillis = billingDate
    }
    
    return when (pattern) {
        PaymentPattern.IMMEDIATE -> calendar.timeInMillis
        PaymentPattern.END_OF_MONTH -> {
            calendar.set(Calendar.DAY_OF_MONTH, 1)
            calendar.add(Calendar.MONTH, 1)
            calendar.add(Calendar.DAY_OF_MONTH, -1)
            calendar.timeInMillis
        }
        PaymentPattern.THIRTY_DAYS -> {
            calendar.add(Calendar.DAY_OF_MONTH, 30)
            calendar.timeInMillis
        }
        PaymentPattern.SIXTY_DAYS -> {
            calendar.add(Calendar.DAY_OF_MONTH, 60)
            calendar.timeInMillis
        }
        else -> calendar.timeInMillis
    }
}

同期フロー

オフライン時

  1. スマホアプリで見積/納品/請求/領収書を作成
  2. SQLiteに自動保存
  3. PDF生成・送信メール等

ネットワーク接続時

  1. 未同期ドキュメントを検出
  2. REST APIに送信
  3. API が Odoo に登録
  4. 同期完了後、ローカルの synced フラグを更新
  5. Odoo で売掛金管理・レポート生成

支払条件パターン

パターン 説明 計算方式
即支払い 当日支払い 請求日
末締め翌月末 末締めで翌月末払い 翌月末日
30日後 請求日から30日後 請求日 + 30日
60日後 請求日から60日後 請求日 + 60日
カスタム 任意設定 ユーザーが指定

セキュリティ

  • API キーは環境変数で管理
  • HTTPS通信を推奨本番環境
  • トークン認証で API アクセス制限
  • Odoo への認証も環境変数化

トラブルシューティング

API接続エラー

# ヘルスチェック
curl http://localhost:8000/api/v1/health -H "X-API-Key: your_key"

# ログ確認
docker-compose logs api

Odoo連携エラー

# Odooログ
docker-compose logs odoo

# DBテーブル確認
docker-compose exec postgres psql -U odoo -d odoo -c "\dt"

Android PDF生成エラー

  • ストレージパーミッション確認
  • 外部ストレージ空き容量確認
  • iText/Apache POI の対応バージョン確認

今後の実装予定

  • Odoo REST API 直接連携
  • Nextcloud WebDAV バックアップ統合
  • 複数ユーザー・デバイス同期
  • オフライン時の競合解決
  • 売掛金ダッシュボード
  • Web UIPCから Odoo 管理用)
  • カスタム領収書テンプレート
  • 電子署名対応

開発者向け情報

REST API テスト

# FastAPI ドキュメント
http://localhost:8000/docs

DB マイグレーション

# 新しいテーブル追加時
# Android: Migrations クラスを追加
# API: SQLAlchemy モデルを追加 → DB再作成

ライセンス

MIT License

サポート

問題報告は Issues で。