Wrap all 34 resource groups, then harden the request plumbing
Expands coverage to every `Route::resource` group in the v5 API plus `users` (265 methods), and fixes several defects in the shared request path that the new breadth made more exposed. Coverage: - 25 new models, each keeping the `extra` flatten map and the `null_to_default` convention so unmodeled fields round-trip. - `crud_methods!` split out of `resource_methods!` for the three groups with no `/bulk` route, where a generated `bulk_*` would only 405. - COVERAGE.md added as the detailed inventory, verified against `routes/api.php` on v5-stable. Fixes, each confirmed against upstream source or an observed request: - `list_all_*` silently truncated. `BaseController` caps page size at `min(abs(per_page), 5000)`, so the "short page means last page" rule stopped early and returned a partial list as if complete. Termination now prefers `meta.pagination.total_pages` (decoded but previously discarded), and `per_page` is clamped so the fallback stays sound. - `per_page(0)` paged forever: the check `got < 0` has no solution. It now falls back to the default size, with a MAX_PAGES backstop that raises `Error::PaginationLimit` rather than quietly truncating. - Ids were interpolated into paths raw, so a `/` or `?` in an id retargeted the request. All sites now use `seg(id)?`. Escaping alone is insufficient: the WHATWG rules `url` implements decode `%2E` to `.` before resolving dot segments, so an escaped `..` still collapses the path. Empty and dot-only ids are rejected. - No request timeout. reqwest applies none, so a stalled connection hung the caller indefinitely. Adds a 60s default and `with_timeout`. - Base URL is validated at construction; a missing scheme used to 404 on every call instead of failing once. Adds tests/client_requests.rs: 26 tests over the request plumbing against a wiremock loopback server, including regressions for the truncation and non-termination bugs. No network or credentials needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+856
-23
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,26 @@ pub enum Error {
|
||||
|
||||
#[error("invalid api token: {0}")]
|
||||
InvalidToken(String),
|
||||
|
||||
/// An id or action could not be used as a single URL path segment.
|
||||
///
|
||||
/// Raised for values that would change which endpoint is addressed rather
|
||||
/// than identifying a record — an empty id (which would hit the
|
||||
/// collection route) or one made only of dots (which the URL parser
|
||||
/// resolves away). Invoice Ninja ids are hashids, so this only rejects
|
||||
/// input that was never a valid id.
|
||||
#[error("`{0}` cannot be used as a url path segment")]
|
||||
InvalidPathSegment(String),
|
||||
|
||||
/// A `list_all_*` call kept receiving full pages past the point where any
|
||||
/// plausible result set would have ended.
|
||||
///
|
||||
/// This means the server is not paginating the way the client expects —
|
||||
/// typically because it ignores the `page` parameter and keeps replaying
|
||||
/// the same page. Raised instead of returning the partial result, so the
|
||||
/// truncation cannot be mistaken for a complete answer.
|
||||
#[error("pagination did not terminate for {path} after {pages} pages")]
|
||||
PaginationLimit { path: String, pages: u64 },
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
+1
-1
@@ -18,5 +18,5 @@ mod client;
|
||||
mod error;
|
||||
pub mod models;
|
||||
|
||||
pub use client::InvoiceNinjaClient;
|
||||
pub use client::{InvoiceNinjaClient, HOSTED_BASE_URL};
|
||||
pub use error::{ApiErrorBody, Error, Result};
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A connected bank account feed.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct BankIntegration {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub user_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub provider_bank_name: String,
|
||||
/// Returned as an integer rather than a hashed id.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub bank_account_id: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub bank_account_name: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub bank_account_number: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub bank_account_status: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub bank_account_type: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub balance: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub currency: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A transaction imported from a connected bank account.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct BankTransaction {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
/// The upstream transaction identifier, returned as an integer rather
|
||||
/// than a hashed id.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub transaction_id: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub bank_account_id: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub amount: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub currency_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub account_type: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub description: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub category_id: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub category_type: String,
|
||||
/// Either `CREDIT` or `DEBIT`.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub base_type: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A rule for auto-categorising imported bank transactions.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct BankTransactionRule {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub user_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
/// Match clauses. Left untyped: the shape is a nested
|
||||
/// `search_key`/`operator`/`value` object the spec does not define.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub rules: Vec<Value>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub auto_convert: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub matches_on_all: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub applies_to: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub vendor_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub category_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
+22
-22
@@ -5,37 +5,37 @@ use serde_json::{Map, Value};
|
||||
pub struct Client {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub id_number: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub vat_number: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub address1: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub address2: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub city: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub state: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub postal_code: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub country_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub phone: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub website: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub private_notes: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub balance: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub paid_to_date: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub credit_balance: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
@@ -43,7 +43,7 @@ pub struct Client {
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub contacts: Vec<ClientContact>,
|
||||
|
||||
/// Any fields returned by the API that aren't modeled above are preserved here,
|
||||
@@ -56,15 +56,15 @@ pub struct Client {
|
||||
pub struct ClientContact {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub first_name: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub last_name: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub email: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub phone: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_primary: bool,
|
||||
|
||||
#[serde(flatten)]
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A stored payment method for a client.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct ClientGatewayToken {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub token: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub routing_number: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub company_gateway_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_default: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
+72
-1
@@ -1,7 +1,12 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ListResponse<T> {
|
||||
/// Defaulted because not every list-shaped response carries a `data` key:
|
||||
/// a bulk action whose `ids` match nothing returns HTTP 200 with just
|
||||
/// `{"message": "No Invoices Found"}`, which should read as "no results"
|
||||
/// rather than a decode failure.
|
||||
#[serde(default = "Vec::new")]
|
||||
pub data: Vec<T>,
|
||||
#[serde(default)]
|
||||
pub meta: Option<Meta>,
|
||||
@@ -44,6 +49,19 @@ pub struct ListParams {
|
||||
/// Set to 1 to include archived/deleted records.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub status: Option<String>,
|
||||
|
||||
/// Extra query parameters appended verbatim.
|
||||
///
|
||||
/// The typed fields above are only the parameters common to every list
|
||||
/// endpoint; individual resources accept many more (`client_id`,
|
||||
/// `vendor_id`, `number`, `created_at`, `is_deleted`, per-resource
|
||||
/// filters). Use [`param`](Self::param) to set those.
|
||||
///
|
||||
/// Skipped by serde and applied as a separate query pass, because
|
||||
/// `serde_urlencoded` — which backs `reqwest`'s query serialization —
|
||||
/// does not handle `#[serde(flatten)]`ed maps.
|
||||
#[serde(skip)]
|
||||
pub custom: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl ListParams {
|
||||
@@ -80,4 +98,57 @@ impl ListParams {
|
||||
self.status = Some(status.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an arbitrary query parameter, for filters the typed setters do
|
||||
/// not cover.
|
||||
///
|
||||
/// ```
|
||||
/// use invoiceninjars::models::ListParams;
|
||||
///
|
||||
/// let params = ListParams::new()
|
||||
/// .per_page(100)
|
||||
/// .param("client_id", "abc123")
|
||||
/// .param("is_deleted", "false");
|
||||
/// ```
|
||||
///
|
||||
/// Can be called more than once with the same key; both pairs are sent.
|
||||
pub fn param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
||||
self.custom.push((key.into(), value.into()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes `null` as `T::default()` instead of failing.
|
||||
///
|
||||
/// `#[serde(default)]` alone only covers a *missing* key — a key that is
|
||||
/// present with an explicit `null` still errors for non-`Option` types.
|
||||
/// Invoice Ninja returns `null` for plenty of nominally-string fields, so the
|
||||
/// models pair `default` with this to tolerate both shapes.
|
||||
pub(crate) fn null_to_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: Deserialize<'de> + Default,
|
||||
{
|
||||
Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Body for a bulk action (`POST /{resource}/bulk`).
|
||||
///
|
||||
/// The action set varies by resource; `archive`, `restore`, and `delete` work
|
||||
/// everywhere, and are what the generated `bulk_*` methods are typed for.
|
||||
/// See [`crate::InvoiceNinjaClient::bulk_invoices`] for the actions whose
|
||||
/// responses are *not* a list of entities.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct BulkRequest {
|
||||
pub action: String,
|
||||
pub ids: Vec<String>,
|
||||
}
|
||||
|
||||
impl BulkRequest {
|
||||
pub fn new(action: impl Into<String>, ids: &[impl AsRef<str>]) -> Self {
|
||||
Self {
|
||||
action: action.into(),
|
||||
ids: ids.iter().map(|id| id.as_ref().to_string()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A company (tenant).
|
||||
///
|
||||
/// The API returns well over a hundred fields here; this models the
|
||||
/// identifying and structural ones and leaves the rest — including every
|
||||
/// `enable_*`/`show_*` feature toggle — to [`extra`](Self::extra), which
|
||||
/// still round-trips them on update.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct Company {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub company_key: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub size_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub industry_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub portal_mode: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub subdomain: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub portal_domain: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub logo: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub db: String,
|
||||
/// The company settings blob. Left untyped — `CompanySettings` is the
|
||||
/// largest object in the API.
|
||||
#[serde(default)]
|
||||
pub settings: Value,
|
||||
/// Custom field label definitions.
|
||||
#[serde(default)]
|
||||
pub custom_fields: Value,
|
||||
/// Bitmask of enabled modules.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub enabled_modules: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub enabled_tax_rates: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub enabled_item_tax_rates: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub enabled_expense_tax_rates: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub first_day_of_week: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub first_month_of_year: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub slack_webhook_url: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub google_analytics_key: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub session_timeout: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub markdown_enabled: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_can_register: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_large: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_disabled: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub calculate_taxes: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A configured payment gateway.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct CompanyGateway {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub gateway_key: String,
|
||||
/// Bitmask of accepted card types.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub accepted_credit_cards: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub require_billing_address: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub require_shipping_address: bool,
|
||||
/// An encrypted JSON *string*, not an object — decrypted server-side, so
|
||||
/// it is opaque here.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub config: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub update_details: bool,
|
||||
/// Per-card fee and limit rules; shape varies by gateway.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub fees_and_limits: Vec<Value>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// An API token. Served from `/tokens`.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct CompanyToken {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
/// Only returned in full when the token is first created.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub token: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_system: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A PDF template.
|
||||
///
|
||||
/// The `design` field itself (the HTML section bodies) is left to
|
||||
/// [`extra`](Self::extra): the spec types it as a string, but the API
|
||||
/// returns a nested object of `header`/`body`/`footer` sections.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct Design {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_custom: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_active: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub deleted_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A document (file) attached to another entity.
|
||||
///
|
||||
/// Documents are created by uploading to the owning entity
|
||||
/// (`PUT /{resource}/{id}/upload`, multipart) rather than by posting to
|
||||
/// `/documents`, so this crate exposes read, update, delete, and download
|
||||
/// but no `create_document`.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct Document {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub user_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub assigned_user_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub project_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub vendor_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub url: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub preview: String,
|
||||
/// File extension, e.g. `pdf`.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub r#type: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub disk: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub hash: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_default: bool,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub deleted_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
+17
-17
@@ -5,39 +5,39 @@ use serde_json::{Map, Value};
|
||||
pub struct Expense {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub vendor_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub invoice_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub amount: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub exchange_rate: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub public_notes: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub private_notes: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub date: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub payment_date: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub transaction_reference: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub category_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_name1: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_rate1: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_name2: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_rate2: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub should_be_invoiced: bool,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct ExpenseCategory {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub user_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A named settings group that clients can be assigned to.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct GroupSetting {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub user_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
/// The settings override blob. Left untyped — Invoice Ninja's
|
||||
/// `CompanySettings` object has well over a hundred fields.
|
||||
#[serde(default)]
|
||||
pub settings: Value,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
+26
-26
@@ -5,35 +5,35 @@ use serde_json::{Map, Value};
|
||||
pub struct Invoice {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub number: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub date: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub due_date: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub po_number: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub status_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub amount: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub balance: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub discount: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_amount_discount: bool,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub public_notes: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub private_notes: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub terms: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub footer: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
@@ -41,7 +41,7 @@ pub struct Invoice {
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub line_items: Vec<InvoiceItem>,
|
||||
|
||||
#[serde(flatten)]
|
||||
@@ -50,25 +50,25 @@ pub struct Invoice {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct InvoiceItem {
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub product_key: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub notes: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub cost: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub quantity: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub discount: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_name1: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_rate1: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_name2: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_rate2: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub line_total: f64,
|
||||
|
||||
#[serde(flatten)]
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A physical address attached to a client or vendor.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct Location {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub address1: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub address2: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub city: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub state: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub postal_code: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub country_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub user_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub assigned_user_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub vendor_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_shipping_location: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
+49
-1
@@ -1,19 +1,67 @@
|
||||
mod bank_integration;
|
||||
mod bank_transaction;
|
||||
mod bank_transaction_rule;
|
||||
mod client;
|
||||
mod client_gateway_token;
|
||||
mod common;
|
||||
mod company;
|
||||
mod company_gateway;
|
||||
mod company_token;
|
||||
mod design;
|
||||
mod document;
|
||||
mod expense;
|
||||
mod expense_category;
|
||||
mod group_setting;
|
||||
mod invoice;
|
||||
mod location;
|
||||
mod payment;
|
||||
mod payment_term;
|
||||
mod product;
|
||||
mod project;
|
||||
mod purchase_order;
|
||||
mod recurring_expense;
|
||||
mod recurring_invoice;
|
||||
mod subscription;
|
||||
mod system_log;
|
||||
mod tag;
|
||||
mod task;
|
||||
mod task_scheduler;
|
||||
mod task_status;
|
||||
mod tax_rate;
|
||||
mod user;
|
||||
mod vendor;
|
||||
mod webhook;
|
||||
|
||||
pub use bank_integration::BankIntegration;
|
||||
pub use bank_transaction::BankTransaction;
|
||||
pub use bank_transaction_rule::BankTransactionRule;
|
||||
pub use client::{Client, ClientContact};
|
||||
pub use common::{ItemResponse, ListParams, ListResponse, Meta, Pagination};
|
||||
pub use client_gateway_token::ClientGatewayToken;
|
||||
pub use common::{BulkRequest, ItemResponse, ListParams, ListResponse, Meta, Pagination};
|
||||
pub use company::Company;
|
||||
pub use company_gateway::CompanyGateway;
|
||||
pub use company_token::CompanyToken;
|
||||
pub use design::Design;
|
||||
pub use document::Document;
|
||||
pub use expense::Expense;
|
||||
pub use expense_category::ExpenseCategory;
|
||||
pub use group_setting::GroupSetting;
|
||||
pub use invoice::{Credit, Invoice, InvoiceItem, Quote};
|
||||
pub use location::Location;
|
||||
pub use payment::{Payment, PaymentInvoice};
|
||||
pub use payment_term::PaymentTerm;
|
||||
pub use product::Product;
|
||||
pub use project::Project;
|
||||
pub use purchase_order::PurchaseOrder;
|
||||
pub use recurring_expense::RecurringExpense;
|
||||
pub use recurring_invoice::{RecurringInvoice, RecurringQuote};
|
||||
pub use subscription::Subscription;
|
||||
pub use system_log::SystemLog;
|
||||
pub use tag::Tag;
|
||||
pub use task::Task;
|
||||
pub use task_scheduler::TaskScheduler;
|
||||
pub use task_status::TaskStatus;
|
||||
pub use tax_rate::TaxRate;
|
||||
pub use user::User;
|
||||
pub use vendor::{Vendor, VendorContact};
|
||||
pub use webhook::Webhook;
|
||||
|
||||
+10
-10
@@ -5,23 +5,23 @@ use serde_json::{Map, Value};
|
||||
pub struct Payment {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub amount: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub applied: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub refunded: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub date: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub transaction_reference: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub type_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub private_notes: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
@@ -29,7 +29,7 @@ pub struct Payment {
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub invoices: Vec<PaymentInvoice>,
|
||||
|
||||
#[serde(flatten)]
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct PaymentTerm {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
/// Human-readable form of the term, e.g. `NET 30`.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub num_days: i64,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
+10
-10
@@ -5,25 +5,25 @@ use serde_json::{Map, Value};
|
||||
pub struct Product {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub product_key: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub notes: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub cost: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub price: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub quantity: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_name1: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_rate1: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_name2: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_rate2: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
|
||||
+10
-10
@@ -5,25 +5,25 @@ use serde_json::{Map, Value};
|
||||
pub struct Project {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub number: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub task_rate: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub due_date: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub budgeted_hours: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub color: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub public_notes: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub private_notes: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use super::invoice::InvoiceItem;
|
||||
|
||||
/// A purchase order. Structurally close to an invoice, but addressed to a
|
||||
/// vendor rather than a client.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct PurchaseOrder {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub vendor_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub invoice_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub project_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub number: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub po_number: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub quote_number: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub status_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub due_date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub partial_due_date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub last_sent_date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub amount: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub balance: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub paid_to_date: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub discount: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub partial: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub exchange_rate: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_amount_discount: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub uses_inclusive_taxes: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub public_notes: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub private_notes: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub terms: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub footer: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub currency_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub design_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub last_viewed: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub line_items: Vec<InvoiceItem>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A recurring expense template: an expense plus the recurrence fields
|
||||
/// (`frequency_id`, `remaining_cycles`, `next_send_date`).
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct RecurringExpense {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub vendor_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub invoice_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub project_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub category_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub number: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub amount: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub foreign_amount: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub exchange_rate: f64,
|
||||
/// Integer enum matching [`RecurringInvoice::frequency_id`].
|
||||
///
|
||||
/// [`RecurringInvoice::frequency_id`]: super::RecurringInvoice::frequency_id
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub frequency_id: i64,
|
||||
/// Number of cycles left; `-1` means it recurs indefinitely.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub remaining_cycles: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub payment_date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub last_sent_date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub next_send_date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub public_notes: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub private_notes: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub transaction_reference: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_name1: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_rate1: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_name2: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_rate2: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_name3: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub tax_rate3: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub should_be_invoiced: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub uses_inclusive_taxes: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use super::invoice::InvoiceItem;
|
||||
|
||||
/// A recurring invoice template. Shares the invoices table with [`Invoice`],
|
||||
/// plus the recurrence fields (`frequency_id`, `remaining_cycles`,
|
||||
/// `next_send_date`, auto-billing).
|
||||
///
|
||||
/// [`Invoice`]: super::Invoice
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct RecurringInvoice {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub vendor_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub project_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub number: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub po_number: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub status_id: String,
|
||||
/// How often the invoice recurs. Invoice Ninja returns this as an integer
|
||||
/// enum (1 = daily, 2 = weekly, … ), not a human-readable string.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub frequency_id: i64,
|
||||
/// Number of cycles left to send; `-1` means it recurs indefinitely.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub remaining_cycles: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub auto_bill: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub auto_bill_enabled: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub due_date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub next_send_date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub last_sent_date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub amount: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub balance: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub paid_to_date: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub discount: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub partial: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub exchange_rate: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_amount_discount: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub uses_inclusive_taxes: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub public_notes: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub private_notes: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub terms: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub footer: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub design_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub last_viewed: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub line_items: Vec<InvoiceItem>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
|
||||
/// Recurring quotes share the recurring-invoice schema (the API's
|
||||
/// `RecurringQuote` fields are a strict subset of `RecurringInvoice`), so the
|
||||
/// `/recurring_quotes` endpoint returns the same JSON shape.
|
||||
pub type RecurringQuote = RecurringInvoice;
|
||||
@@ -0,0 +1,57 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A subscription (billing plan) definition.
|
||||
///
|
||||
/// Note that unlike [`RecurringInvoice::frequency_id`], the API returns this
|
||||
/// resource's `frequency_id` as a string.
|
||||
///
|
||||
/// [`RecurringInvoice::frequency_id`]: super::RecurringInvoice::frequency_id
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct Subscription {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub product_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub recurring_invoice_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub frequency_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub auto_bill: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub promo_code: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub promo_discount: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_recurring: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_amount_discount: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub allow_cancellation: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub per_seat_enabled: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub max_seats_limit: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub trial_enabled: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub trial_duration: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub allow_query_overrides: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub allow_plan_changes: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub refund_period: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A server-side log entry (gateway responses, mail failures, and so on).
|
||||
///
|
||||
/// Note that unlike every other resource, this one's `created_at` and
|
||||
/// `updated_at` are returned as date **strings**, not integer timestamps.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct SystemLog {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub user_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub event_id: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub category_id: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub type_id: i64,
|
||||
/// The logged payload; shape depends on the event.
|
||||
#[serde(default)]
|
||||
pub log: Value,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub created_at: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub updated_at: String,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct Tag {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub entity_type: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
/// Hex colour value.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub color: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
+9
-9
@@ -5,24 +5,24 @@ use serde_json::{Map, Value};
|
||||
pub struct Task {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub client_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub invoice_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub project_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub description: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub rate: f64,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub status_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_running: bool,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
/// JSON-encoded array of `[start_timestamp, end_timestamp]` pairs, as returned by the API.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub time_log: String,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A scheduled job (recurring report emails, and similar).
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct TaskScheduler {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub paused: bool,
|
||||
/// Frequency, e.g. `DAY`, `WEEK`, `MONTH`.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub repeat_every: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub start_from: i64,
|
||||
/// The job to run, e.g. `email_report`.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub job: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub date_range: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub date_key: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub start_date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub end_date: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub report_keys: Vec<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct TaskStatus {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
/// Hex colour value, e.g. `#000000`.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub color: String,
|
||||
/// Position of this status in the kanban board ordering.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub task_status_order: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct TaxRate {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
/// The rate as a percentage, e.g. `10.0` for 10%.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub rate: f64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A user account.
|
||||
///
|
||||
/// Every `/users` endpoint except the index listing sits behind the API's
|
||||
/// `password_protected` middleware, so reads of a single user and all writes
|
||||
/// need an `X-API-PASSWORD` header — see
|
||||
/// [`InvoiceNinjaClient::with_api_password`](crate::InvoiceNinjaClient::with_api_password).
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub first_name: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub last_name: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub email: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub phone: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub signature: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub avatar: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub language_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub accepted_terms_version: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub oauth_user_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub oauth_provider_id: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub verified_phone_number: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub has_password: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub last_confirmed_email_address: String,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub passkey_enabled: bool,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub passkey_count: i64,
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
+19
-19
@@ -5,31 +5,31 @@ use serde_json::{Map, Value};
|
||||
pub struct Vendor {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub id_number: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub vat_number: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub address1: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub address2: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub city: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub state: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub postal_code: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub country_id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub phone: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub website: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub private_notes: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_deleted: bool,
|
||||
#[serde(default)]
|
||||
pub archived_at: Option<i64>,
|
||||
@@ -37,7 +37,7 @@ pub struct Vendor {
|
||||
pub created_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub updated_at: Option<i64>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub contacts: Vec<VendorContact>,
|
||||
|
||||
/// Any fields returned by the API that aren't modeled above are preserved here,
|
||||
@@ -50,15 +50,15 @@ pub struct Vendor {
|
||||
pub struct VendorContact {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub first_name: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub last_name: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub email: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub phone: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub is_primary: bool,
|
||||
|
||||
#[serde(flatten)]
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// An outbound webhook registration (the API calls these "subscriptions"
|
||||
/// internally, but they are served from `/webhooks`).
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct Webhook {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
/// The event this hook fires on, as an integer-enum string.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub event_id: String,
|
||||
/// The URL Invoice Ninja posts to.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub target_url: String,
|
||||
/// Payload format: `JSON` or `UBL`.
|
||||
#[serde(default, deserialize_with = "super::common::null_to_default")]
|
||||
pub format: String,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Map<String, Value>,
|
||||
}
|
||||
Reference in New Issue
Block a user