2026-08-26 09:15:35 -05:00
2026-08-26 09:15:35 -05:00
2026-08-26 09:15:35 -05:00
2026-08-26 09:15:35 -05:00
2026-08-26 09:15:35 -05:00
2026-08-26 09:15:35 -05:00
2026-08-26 09:15:35 -05:00

Order Manager

A PyQt6 desktop app that independently pulls orders from JIRA and ShipStation, tracks them by company (Signify Health / Oak Street Health) and ticket number, and gives you a dashboard of what's come in today. Odoo integration is file-based for now (CSV export) since the live Odoo side is still in development.

Setup

  1. pip install -r requirements.txt
  2. Run it once: python main.py (this creates .env from .env.example)
  3. Click Settings and fill in:
    • JIRA: Site URL, Email, API Token, JQL (defaults to tickets created today - adjust to match your project/label). The deliverable field IDs are already defaulted from your export (customfield_10573,customfield_10570 for Signify; customfield_12790,customfield_13021 for Oak Street) - update these if your field IDs ever change.
    • ShipStation: API Key. The store IDs are already defaulted (se-221889 Signify, se-367672 Oak Street) - just add your key.
    • Companies: SKU prefix -> company mapping (defaults to SH:Signify Health,OK:Oak Street Health) and the ticket number pattern (defaults to AR-######-style).
  4. Click Import from JIRA and/or Import from ShipStation - they run independently, each in the background so the UI stays responsive.
  5. Check the Dashboard tab for totals by company/source and how many tickets are matched across both systems vs. only seen in one.
  6. Use All Orders to filter by company/source or search by ticket number/SKU, and Export Visible Orders to Odoo CSV to hand off whatever's currently filtered.

Orders are cached locally in SQLite (orders.db). Re-importing updates existing orders rather than duplicating them (matched on source + source ID).

How company/ticket matching works

  • JIRA orders: each company has its own pair of "deliverable" custom fields in JIRA (e.g. Signify's Deliverables + Hardware Needed; Oak Street's own versions). A ticket can list several deliverables (customfield_10573, etc. - configurable via JIRA_SIGNIFY_SKU_FIELDS / JIRA_OAKSTREET_SKU_FIELDS). Each deliverable value looks like SH011: Shipping - Return Label iPad - Physical in Box - the app pulls out just the SH011 code as the SKU and keeps the description for the summary. Company is then resolved from the SKU prefix (SH/OK) via COMPANY_SKU_MAP, same as before. Since these tickets' actual JIRA "Summary" field is usually blank, the app falls back to the joined deliverable descriptions for the Summary column when there's nothing else there.
  • ShipStation orders: company is derived from which store the order lives in (SHIPSTATION_STORE_MAP), since each company has its own store/UPS account.
  • Ticket number (AR-######) is the JIRA issue key directly on JIRA-sourced orders. On ShipStation-sourced orders, since V2's API doesn't have a dedicated "orders" endpoint with a guaranteed order-number field, the app scans the whole shipment payload for the configured pattern (TICKET_NUMBER_REGEX). Once you see what a real ShipStation payload for one of your shipments looks like, this can be tightened to read one specific field for speed/reliability - just point me at where it actually shows up.

A note on the ShipStation V2 API

ShipStation's V2 API doesn't expose a dedicated "list orders" endpoint the way the older V1 API did - it's built around /v2/shipments. This app pulls today's shipments from that endpoint and filters client-side to your two configured store IDs. If your account's shipments don't carry an items/SKU array the way we expect (this can depend on how orders reach ShipStation), the company will still resolve correctly (from store_id, which is reliable) even if the SKU column comes back empty - worth checking against a real imported order early on.

Moving storage to your MariaDB LXC later

In Settings (or directly in .env), change:

DB_URL=mysql+pymysql://user:password@<lxc-ip>:3306/order_manager

and pip install pymysql. No code changes needed. Note: this is a local cache rebuilt from JIRA/ShipStation, so if you change DB_URL or the schema changes in a future update, it's safe to just delete orders.db and re-import rather than migrate it.

Project layout

main.py                        entry point
app/
  config.py                    reads/writes .env, defines the settings schema
  database.py                  SQLAlchemy engine/session (SQLite now, MariaDB later)
  models.py                    Order table - company/ticket_number/skus + shared shape
  companies.py                 SKU-prefix and store-ID -> company resolution
  workers.py                   background thread(s) for API calls + DB save/load/stats
  services/
    base.py                    OrderService interface - implement this for new sources
    jira_service.py            JIRA REST API -> NormalizedOrder (SKU field, company)
    shipstation_service.py     ShipStation V2 (/v2/shipments) -> NormalizedOrder
    odoo_export.py             CSV export for the (in-development) Odoo import template
    __init__.py                SERVICE_REGISTRY - register new sources here
  ui/
    main_window.py             tabs, toolbar (independent import buttons), export
    settings_dialog.py         auto-built from config.SETTINGS_SCHEMA
    widgets/
      dashboard.py             summary stat cards
      orders_table.py          sortable/filterable Qt table for orders

Adding real Odoo API access next

  1. Add settings to app/config.py -> SETTINGS_SCHEMA (Odoo group).
  2. Create app/services/odoo_service.py. If it's pull-based (Odoo has orders you need to see), subclass OrderService like the other two. If it's push-based (you're sending orders to Odoo), it doesn't need to subclass OrderService - a push_orders(orders) method is fine, called from a new toolbar action the same way _on_export_clicked calls export_orders_to_csv.
  3. Register/wire it up the same way JIRA and ShipStation are.

Since everything funnels through the same Order table, the table view, dashboard, and settings UI don't need to change as sources are added.

S
Description
Jira Madness 2.0
Readme
558 KiB
Languages
Python 100%