Files
invoiceninja-cli/src/commands/purchase_orders.rs
T
awkawbandClaude Sonnet 5 a0a629cbfe Add credits, projects, tasks, vendors, and purchase orders
Five more resources following clients.rs's template exactly, same as
the previous batch. credits.rs is otherwise identical to quotes.rs
since Credit is also a type alias for Invoice in the SDK (same table,
distinguished by entity_type upstream).

Verified against a live local server: full create/list/get/update/
delete round trip for all five.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-26 16:49:39 -04:00

142 lines
4.2 KiB
Rust

//! `purchase-orders` subcommand. Same shape as `clients.rs` — see the
//! "Adding a resource" section in CLAUDE.md.
use invoiceninja_sdk::models::{ListParams, PurchaseOrder};
use invoiceninja_sdk::InvoiceNinjaClient;
use crate::body;
use crate::cli::{Action, BodyArgs, ListArgs, OutputFormat, ResourceArgs};
use crate::output;
pub async fn run(
client: &InvoiceNinjaClient,
args: ResourceArgs,
format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
match args.action {
Action::List(list_args) => list(client, list_args, format).await,
Action::Get { id } => get(client, &id, format).await,
Action::Create(body_args) => create(client, body_args, format).await,
Action::Update { id, body } => update(client, &id, body, format).await,
Action::Delete { id } => delete(client, &id).await,
}
}
async fn list(
client: &InvoiceNinjaClient,
args: ListArgs,
format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
let mut params = ListParams::new();
if let Some(page) = args.page {
params = params.page(page);
}
if let Some(per_page) = args.per_page {
params = params.per_page(per_page);
}
if let Some(sort) = args.sort {
params = params.sort(sort);
}
if let Some(filter) = args.filter {
params = params.filter(filter);
}
if let Some(status) = args.status {
params = params.status(status);
}
if let Some(include) = args.include {
params = params.include(include);
}
for (k, v) in args.params {
params = params.param(k, v);
}
let purchase_orders = if args.all {
client.list_all_purchase_orders(&params).await?
} else {
client.list_purchase_orders(&params).await?
};
render(&purchase_orders, format)
}
async fn get(
client: &InvoiceNinjaClient,
id: &str,
format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
let found = client.get_purchase_order(id).await?;
render(std::slice::from_ref(&found), format)
}
async fn create(
client: &InvoiceNinjaClient,
body_args: BodyArgs,
format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
let value = body::load(&body_args)?;
let model: PurchaseOrder = serde_json::from_value(value)?;
let created = client.create_purchase_order(&model).await?;
render(std::slice::from_ref(&created), format)
}
async fn update(
client: &InvoiceNinjaClient,
id: &str,
body_args: BodyArgs,
format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
let patch = body::load(&body_args)?;
let current = client.get_purchase_order(id).await?;
let merged = body::merge_shallow(serde_json::to_value(&current)?, patch);
let model: PurchaseOrder = serde_json::from_value(merged)?;
let updated = client.update_purchase_order(id, &model).await?;
render(std::slice::from_ref(&updated), format)
}
async fn delete(client: &InvoiceNinjaClient, id: &str) -> Result<(), Box<dyn std::error::Error>> {
client.delete_purchase_order(id).await?;
println!("deleted {id}");
Ok(())
}
fn render(
purchase_orders: &[PurchaseOrder],
format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
match format {
OutputFormat::Json => output::print_json(purchase_orders)?,
OutputFormat::Table => print_table(purchase_orders),
}
Ok(())
}
fn print_table(purchase_orders: &[PurchaseOrder]) {
if purchase_orders.is_empty() {
println!("(no records)");
return;
}
println!(
"{:<20} {:<15} {:<20} {:<8} {:>12} {:>12}",
"id", "number", "vendor_id", "status", "amount", "balance"
);
for po in purchase_orders {
println!(
"{:<20} {:<15} {:<20} {:<8} {:>12.2} {:>12.2}",
po.id.as_deref().unwrap_or(""),
truncate(&po.number, 15),
truncate(&po.vendor_id, 20),
po.status_id,
po.amount,
po.balance
);
}
}
fn truncate(s: &str, max: usize) -> String {
if s.chars().count() <= max {
s.to_string()
} else {
s.chars().take(max.saturating_sub(1)).collect::<String>() + "…"
}
}