125 lines
6.1 KiB
Markdown
125 lines
6.1 KiB
Markdown
# 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.
|