Pin the response shapes captured from a real server
Twice now a mock written from the upstream source passed while a real server disagreed — the system-log timestamps and the `/subscriptions/steps` shape — and `frequency_id` and several empty-string fields were wrong the same way. Reading the source harder was not going to find the next one. `tests/fixtures/*.json` hold the *shapes* of real responses: every key, with its JSON type, and no values at all. `real_shapes.rs` decodes each one and checks that a decode plus re-serialise loses no key, so a field the models silently drop now fails a test. It also refuses a fixture holding any real value, and refuses a fixture that no test reads. `seed_sample_data.rs` fills a disposable instance with one row of each kind and `capture_shapes.rs` captures the shapes back out with GET requests only, so the set can be refreshed when a divergence turns up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,167 @@
|
|||||||
|
//! Records the *shape* of real Invoice Ninja responses as test fixtures.
|
||||||
|
//!
|
||||||
|
//! Mock servers written from the upstream source have been wrong about the
|
||||||
|
//! real thing before (system-log timestamps, `/subscriptions/steps`), so the
|
||||||
|
//! offline suite benefits from fixtures that came from a real server. This
|
||||||
|
//! writes them without writing any data: every string becomes `""`, every
|
||||||
|
//! number `0`, every bool `false`, and an array keeps only its first element.
|
||||||
|
//! What survives is the key names and the JSON types, which is what decoding
|
||||||
|
//! depends on. `tests/real_shapes.rs` refuses to pass if a fixture holds
|
||||||
|
//! anything else.
|
||||||
|
//!
|
||||||
|
//! GET requests only, so it is safe against any instance, but it still reads
|
||||||
|
//! real records in memory. Review the diff before committing.
|
||||||
|
//!
|
||||||
|
//! ```sh
|
||||||
|
//! IN_BASE_URL=http://localhost:8080/api/v1 IN_API_TOKEN=... \
|
||||||
|
//! cargo run --example capture_shapes
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! A resource with no rows on the instance writes nothing and is reported as
|
||||||
|
//! skipped, since an empty list says nothing about a row's shape. Populate a
|
||||||
|
//! disposable instance and re-run to capture more.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
/// Resources captured as a one-row list. The name doubles as the fixture
|
||||||
|
/// filename.
|
||||||
|
const LISTS: &[&str] = &[
|
||||||
|
"activities",
|
||||||
|
"bank_integrations",
|
||||||
|
"bank_transaction_rules",
|
||||||
|
"bank_transactions",
|
||||||
|
"client_gateway_tokens",
|
||||||
|
"clients",
|
||||||
|
"companies",
|
||||||
|
"company_gateways",
|
||||||
|
"credits",
|
||||||
|
"designs",
|
||||||
|
"documents",
|
||||||
|
"expense_categories",
|
||||||
|
"expenses",
|
||||||
|
"group_settings",
|
||||||
|
"invoices",
|
||||||
|
"locations",
|
||||||
|
"payment_terms",
|
||||||
|
"payments",
|
||||||
|
"products",
|
||||||
|
"projects",
|
||||||
|
"purchase_orders",
|
||||||
|
"quotes",
|
||||||
|
"recurring_expenses",
|
||||||
|
"recurring_invoices",
|
||||||
|
"recurring_quotes",
|
||||||
|
"subscriptions",
|
||||||
|
"system_logs",
|
||||||
|
"tags",
|
||||||
|
"task_schedulers",
|
||||||
|
"task_statuses",
|
||||||
|
"tasks",
|
||||||
|
"tax_rates",
|
||||||
|
"tokens",
|
||||||
|
"users",
|
||||||
|
"vendors",
|
||||||
|
"webhooks",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Endpoints with no `data` envelope, captured whole.
|
||||||
|
const BARE: &[(&str, &str)] = &[
|
||||||
|
("statics", "statics"),
|
||||||
|
("health_check", "health_check"),
|
||||||
|
("subscription_steps", "subscriptions/steps"),
|
||||||
|
("activity_feed", "activities?rows=1&reactv2=1"),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Replaces every value with a placeholder of the same JSON type.
|
||||||
|
fn shape(value: &Value) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Null => Value::Null,
|
||||||
|
Value::Bool(_) => json!(false),
|
||||||
|
Value::Number(n) if n.is_f64() => json!(0.0),
|
||||||
|
Value::Number(_) => json!(0),
|
||||||
|
Value::String(_) => json!(""),
|
||||||
|
// Only the first element: a list's rows share a shape, and keeping
|
||||||
|
// all of them would just be size.
|
||||||
|
Value::Array(items) => Value::Array(items.first().map(shape).into_iter().collect()),
|
||||||
|
Value::Object(map) => {
|
||||||
|
Value::Object(map.iter().map(|(k, v)| (k.clone(), shape(v))).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let base = std::env::var("IN_BASE_URL").expect("set IN_BASE_URL");
|
||||||
|
let token = std::env::var("IN_API_TOKEN").expect("set IN_API_TOKEN");
|
||||||
|
let base = base.trim_end_matches('/');
|
||||||
|
let out = Path::new("tests/fixtures");
|
||||||
|
fs::create_dir_all(out).expect("create tests/fixtures");
|
||||||
|
|
||||||
|
let http = reqwest::Client::new();
|
||||||
|
let get = |path: String| {
|
||||||
|
let http = http.clone();
|
||||||
|
let token = token.clone();
|
||||||
|
let url = format!("{base}/{path}");
|
||||||
|
async move {
|
||||||
|
let response = http
|
||||||
|
.get(url)
|
||||||
|
.header("X-Api-Token", token)
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.header("X-Requested-With", "XMLHttpRequest")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let status = response.status();
|
||||||
|
let body = response.text().await.map_err(|e| e.to_string())?;
|
||||||
|
if !status.is_success() {
|
||||||
|
return Err(format!("HTTP {status}"));
|
||||||
|
}
|
||||||
|
serde_json::from_str::<Value>(&body).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for name in LISTS {
|
||||||
|
// Deleted and archived rows included: on a disposable instance they
|
||||||
|
// are often all there is.
|
||||||
|
// `system_logs` answers the status filter with a 500, so fall back to
|
||||||
|
// the plain list when the filtered one fails.
|
||||||
|
let filtered = format!("{name}?per_page=1&status=active,archived,deleted");
|
||||||
|
let result = match get(filtered).await {
|
||||||
|
Ok(body) => Ok(body),
|
||||||
|
Err(_) => get(format!("{name}?per_page=1")).await,
|
||||||
|
};
|
||||||
|
match result {
|
||||||
|
Ok(body) => match body
|
||||||
|
.get("data")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.and_then(|d| d.first())
|
||||||
|
{
|
||||||
|
Some(row) => {
|
||||||
|
write(out, name, &json!({ "data": [shape(row)] }));
|
||||||
|
println!(" captured {name}");
|
||||||
|
}
|
||||||
|
None => println!(" skipped {name}: no rows on this instance"),
|
||||||
|
},
|
||||||
|
Err(e) => println!(" FAILED {name}: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (name, path) in BARE {
|
||||||
|
match get((*path).to_string()).await {
|
||||||
|
Ok(body) => {
|
||||||
|
write(out, name, &shape(&body));
|
||||||
|
println!(" captured {name}");
|
||||||
|
}
|
||||||
|
Err(e) => println!(" FAILED {name}: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(dir: &Path, name: &str, value: &Value) {
|
||||||
|
let mut text = serde_json::to_string_pretty(value).expect("serialise fixture");
|
||||||
|
text.push('\n');
|
||||||
|
fs::write(dir.join(format!("{name}.json")), text).expect("write fixture");
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
//! Fills a **disposable** Invoice Ninja instance with one record of each
|
||||||
|
//! common kind, so `capture_shapes` has real rows to take shapes from.
|
||||||
|
//!
|
||||||
|
//! ```sh
|
||||||
|
//! IN_SMOKE_WRITE_HOST=localhost IN_BASE_URL=http://localhost:8080/api/v1 \
|
||||||
|
//! IN_API_TOKEN=... cargo run --example seed_sample_data
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! **Never run this against production.** It creates records, advances
|
||||||
|
//! number sequences, and may fire webhooks. It uses the smoke test's guard:
|
||||||
|
//! `IN_SMOKE_WRITE_HOST` must equal the host in `IN_BASE_URL`. Nothing is
|
||||||
|
//! deleted afterwards; the instance is meant to be thrown away.
|
||||||
|
//!
|
||||||
|
//! Each step is independent: one that fails (a validation rule this script
|
||||||
|
//! did not anticipate) is reported and the rest carry on, with anything that
|
||||||
|
//! needed it skipped.
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
use invoiceninjars::{Error, InvoiceNinjaClient};
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
fn model<T: DeserializeOwned>(value: Value) -> T {
|
||||||
|
serde_json::from_value(value).expect("seed body matches the model")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe(error: &Error) -> String {
|
||||||
|
match error {
|
||||||
|
Error::Api { errors, .. } if !errors.is_empty() => format!("{error} {errors:?}"),
|
||||||
|
_ => error.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs one creation, reporting the outcome and returning the new id.
|
||||||
|
async fn step<T>(name: &str, call: impl Future<Output = invoiceninjars::Result<T>>) -> Option<T> {
|
||||||
|
match call.await {
|
||||||
|
Ok(created) => {
|
||||||
|
println!(" created {name}");
|
||||||
|
Some(created)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!(" FAILED {name}: {}", describe(&e));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> ExitCode {
|
||||||
|
let base_url = std::env::var("IN_BASE_URL").expect("set IN_BASE_URL");
|
||||||
|
let token = std::env::var("IN_API_TOKEN").expect("set IN_API_TOKEN");
|
||||||
|
let host = reqwest::Url::parse(&base_url)
|
||||||
|
.ok()
|
||||||
|
.and_then(|u| u.host_str().map(str::to_string))
|
||||||
|
.unwrap_or_default();
|
||||||
|
if std::env::var("IN_SMOKE_WRITE_HOST").as_deref() != Ok(host.as_str()) {
|
||||||
|
eprintln!("refusing to write: set IN_SMOKE_WRITE_HOST={host} to confirm this instance is disposable");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
let c = InvoiceNinjaClient::new(&base_url, token).expect("client");
|
||||||
|
println!("seeding {host}");
|
||||||
|
|
||||||
|
let line =
|
||||||
|
json!([{ "product_key": "seed-product", "quantity": 1, "cost": 10.0, "notes": "seed" }]);
|
||||||
|
|
||||||
|
let client_id = step(
|
||||||
|
"client",
|
||||||
|
c.create_client(&model(json!({
|
||||||
|
"name": "seed-client",
|
||||||
|
"contacts": [{ "first_name": "Seed", "last_name": "Contact", "email": "seed@example.com" }],
|
||||||
|
}))),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.and_then(|x| x.id);
|
||||||
|
let vendor_id = step(
|
||||||
|
"vendor",
|
||||||
|
c.create_vendor(&model(json!({ "name": "seed-vendor" }))),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.and_then(|x| x.id);
|
||||||
|
|
||||||
|
step(
|
||||||
|
"product",
|
||||||
|
c.create_product(&model(
|
||||||
|
json!({ "product_key": "seed-product", "price": 10.0 }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"tax_rate",
|
||||||
|
c.create_tax_rate(&model(json!({ "name": "seed-tax", "rate": 5.0 }))),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"payment_term",
|
||||||
|
c.create_payment_term(&model(json!({ "name": "seed-term", "num_days": 15 }))),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"expense_category",
|
||||||
|
c.create_expense_category(&model(json!({ "name": "seed-category" }))),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"tag",
|
||||||
|
c.create_tag(&model(
|
||||||
|
json!({ "name": "seed-tag", "entity_type": "client" }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"group_setting",
|
||||||
|
c.create_group_setting(&model(json!({ "name": "seed-group" }))),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"webhook",
|
||||||
|
c.create_webhook(&model(
|
||||||
|
json!({ "event_id": "1", "target_url": "https://example.com/seed-hook" }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"expense",
|
||||||
|
c.create_expense(&model(
|
||||||
|
json!({ "amount": 5.0, "public_notes": "seed", "vendor_id": vendor_id }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"recurring_expense",
|
||||||
|
c.create_recurring_expense(&model(
|
||||||
|
json!({ "amount": 5.0, "frequency_id": 5, "vendor_id": vendor_id }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Some(vendor_id) = &vendor_id {
|
||||||
|
step(
|
||||||
|
"purchase_order",
|
||||||
|
c.create_purchase_order(&model(
|
||||||
|
json!({ "vendor_id": vendor_id, "line_items": line }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(client_id) = client_id else {
|
||||||
|
println!("no client, so the client-dependent records are skipped");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
};
|
||||||
|
|
||||||
|
step(
|
||||||
|
"location",
|
||||||
|
c.create_location(&model(
|
||||||
|
json!({ "name": "seed-location", "client_id": client_id, "address1": "1 Seed St" }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"project",
|
||||||
|
c.create_project(&model(
|
||||||
|
json!({ "name": "seed-project", "client_id": client_id }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"task",
|
||||||
|
c.create_task(&model(
|
||||||
|
json!({ "description": "seed task", "client_id": client_id }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"quote",
|
||||||
|
c.create_quote(&model(
|
||||||
|
json!({ "client_id": client_id, "line_items": line }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"credit",
|
||||||
|
c.create_credit(&model(
|
||||||
|
json!({ "client_id": client_id, "line_items": line }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
step(
|
||||||
|
"recurring_invoice",
|
||||||
|
c.create_recurring_invoice(&model(
|
||||||
|
json!({ "client_id": client_id, "frequency_id": 5, "line_items": line }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
// Expected to 500 on 5.13.50: the `recurring_quotes` table has no
|
||||||
|
// `design_id` column, so the server's own insert fails. Not an SDK bug.
|
||||||
|
step(
|
||||||
|
"recurring_quote",
|
||||||
|
c.create_recurring_quote(&model(
|
||||||
|
json!({ "client_id": client_id, "frequency_id": 5, "line_items": line }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let invoice = step(
|
||||||
|
"invoice",
|
||||||
|
c.create_invoice(&model(
|
||||||
|
json!({ "client_id": client_id, "line_items": line }),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
if let Some(id) = invoice.and_then(|i| i.id) {
|
||||||
|
// A payment can only be applied to an invoice that has been sent.
|
||||||
|
if step("invoice mark_sent", c.mark_invoice_sent(&id))
|
||||||
|
.await
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
step(
|
||||||
|
"payment",
|
||||||
|
c.create_payment(&model(json!({
|
||||||
|
"client_id": client_id,
|
||||||
|
"amount": 5.0,
|
||||||
|
"invoices": [{ "invoice_id": id, "amount": 5.0 }],
|
||||||
|
}))),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
}
|
||||||
Vendored
+29
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"activity_type_id": "",
|
||||||
|
"client_id": "",
|
||||||
|
"company_id": "",
|
||||||
|
"contact_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"credit_id": "",
|
||||||
|
"expense_id": "",
|
||||||
|
"id": "",
|
||||||
|
"invoice_id": "",
|
||||||
|
"ip": "",
|
||||||
|
"is_system": false,
|
||||||
|
"notes": "",
|
||||||
|
"payment_id": "",
|
||||||
|
"purchase_order_id": "",
|
||||||
|
"quote_id": "",
|
||||||
|
"recurring_expense_id": "",
|
||||||
|
"recurring_invoice_id": "",
|
||||||
|
"task_id": "",
|
||||||
|
"token_id": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"vendor_contact_id": "",
|
||||||
|
"vendor_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+24
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"activity_type_id": 0,
|
||||||
|
"created_at": 0,
|
||||||
|
"hashed_id": "",
|
||||||
|
"id": "",
|
||||||
|
"ip": "",
|
||||||
|
"notes": "",
|
||||||
|
"purchase_order": {
|
||||||
|
"hashed_id": "",
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"hashed_id": "",
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"vendor": {
|
||||||
|
"hashed_id": "",
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+114
@@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"address1": "",
|
||||||
|
"address2": "",
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"balance": 0,
|
||||||
|
"city": "",
|
||||||
|
"classification": "",
|
||||||
|
"client_hash": "",
|
||||||
|
"contacts": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"can_sign": false,
|
||||||
|
"cc_only": false,
|
||||||
|
"contact_key": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"email": "",
|
||||||
|
"first_name": "",
|
||||||
|
"id": "",
|
||||||
|
"is_locked": false,
|
||||||
|
"is_primary": false,
|
||||||
|
"last_login": 0,
|
||||||
|
"last_name": "",
|
||||||
|
"link": "",
|
||||||
|
"password": "",
|
||||||
|
"phone": "",
|
||||||
|
"send_email": false,
|
||||||
|
"updated_at": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"country_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"credit_balance": 0,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"display_name": "",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"disk": "",
|
||||||
|
"documentable_id": "",
|
||||||
|
"documentable_type": "",
|
||||||
|
"hash": "",
|
||||||
|
"height": 0,
|
||||||
|
"id": "",
|
||||||
|
"is_default": false,
|
||||||
|
"is_deleted": false,
|
||||||
|
"is_public": false,
|
||||||
|
"link": "",
|
||||||
|
"name": "",
|
||||||
|
"preview": "",
|
||||||
|
"project_id": "",
|
||||||
|
"size": 0,
|
||||||
|
"type": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"url": "",
|
||||||
|
"user_id": "",
|
||||||
|
"vendor_id": "",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"e_invoice": {},
|
||||||
|
"gateway_tokens": [],
|
||||||
|
"group_settings_id": "",
|
||||||
|
"has_valid_vat_number": false,
|
||||||
|
"id": "",
|
||||||
|
"id_number": "",
|
||||||
|
"industry_id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"is_tax_exempt": false,
|
||||||
|
"last_login": 0,
|
||||||
|
"locations": [],
|
||||||
|
"name": "",
|
||||||
|
"number": "",
|
||||||
|
"paid_to_date": 0,
|
||||||
|
"payment_balance": 0,
|
||||||
|
"phone": "",
|
||||||
|
"postal_code": "",
|
||||||
|
"private_notes": "",
|
||||||
|
"public_notes": "",
|
||||||
|
"routing_id": "",
|
||||||
|
"settings": {
|
||||||
|
"currency_id": "",
|
||||||
|
"entity": "",
|
||||||
|
"industry_id": null,
|
||||||
|
"size_id": null
|
||||||
|
},
|
||||||
|
"shipping_address1": "",
|
||||||
|
"shipping_address2": "",
|
||||||
|
"shipping_city": "",
|
||||||
|
"shipping_country_id": "",
|
||||||
|
"shipping_postal_code": "",
|
||||||
|
"shipping_state": "",
|
||||||
|
"size_id": "",
|
||||||
|
"state": "",
|
||||||
|
"tags": [],
|
||||||
|
"tax_info": {},
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"vat_number": "",
|
||||||
|
"website": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+1059
File diff suppressed because it is too large
Load Diff
Vendored
+139
@@ -0,0 +1,139 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"amount": 0,
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"backup": {
|
||||||
|
"adjustable_amount": 0,
|
||||||
|
"cancellation": {
|
||||||
|
"adjustment": 0,
|
||||||
|
"status_id": 0
|
||||||
|
},
|
||||||
|
"child_invoice_ids": [],
|
||||||
|
"document_type": null,
|
||||||
|
"e_invoice_cleared_at": null,
|
||||||
|
"e_invoice_status": null,
|
||||||
|
"guid": "",
|
||||||
|
"notes": null,
|
||||||
|
"parent_invoice_id": null,
|
||||||
|
"parent_invoice_number": null,
|
||||||
|
"redirect": null
|
||||||
|
},
|
||||||
|
"balance": 0,
|
||||||
|
"client_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"custom_surcharge1": 0,
|
||||||
|
"custom_surcharge2": 0,
|
||||||
|
"custom_surcharge3": 0,
|
||||||
|
"custom_surcharge4": 0,
|
||||||
|
"custom_surcharge_tax1": false,
|
||||||
|
"custom_surcharge_tax2": false,
|
||||||
|
"custom_surcharge_tax3": false,
|
||||||
|
"custom_surcharge_tax4": false,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"design_id": "",
|
||||||
|
"discount": 0,
|
||||||
|
"documents": [],
|
||||||
|
"due_date": "",
|
||||||
|
"e_invoice": {},
|
||||||
|
"entity_type": "",
|
||||||
|
"exchange_rate": 0,
|
||||||
|
"footer": "",
|
||||||
|
"has_expenses": false,
|
||||||
|
"has_tasks": false,
|
||||||
|
"id": "",
|
||||||
|
"invitations": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"can_sign": false,
|
||||||
|
"client_contact_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"email_error": "",
|
||||||
|
"email_status": "",
|
||||||
|
"id": "",
|
||||||
|
"key": "",
|
||||||
|
"link": "",
|
||||||
|
"message_id": "",
|
||||||
|
"opened_date": "",
|
||||||
|
"sent_date": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"viewed_date": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invoice_id": "",
|
||||||
|
"is_amount_discount": false,
|
||||||
|
"is_deleted": false,
|
||||||
|
"last_sent_date": "",
|
||||||
|
"line_items": [
|
||||||
|
{
|
||||||
|
"cost": 0,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"discount": 0,
|
||||||
|
"expense_id": "",
|
||||||
|
"gross_line_total": 0,
|
||||||
|
"income_account_id": "",
|
||||||
|
"is_amount_discount": false,
|
||||||
|
"line_total": 0,
|
||||||
|
"net_cost": 0,
|
||||||
|
"notes": "",
|
||||||
|
"product_cost": 0,
|
||||||
|
"product_key": "",
|
||||||
|
"quantity": 0,
|
||||||
|
"sort_id": "",
|
||||||
|
"tags": "",
|
||||||
|
"task_id": "",
|
||||||
|
"tax_amount": 0,
|
||||||
|
"tax_id": "",
|
||||||
|
"tax_name1": "",
|
||||||
|
"tax_name2": "",
|
||||||
|
"tax_name3": "",
|
||||||
|
"tax_rate1": 0,
|
||||||
|
"tax_rate2": 0,
|
||||||
|
"tax_rate3": 0,
|
||||||
|
"type_id": "",
|
||||||
|
"unit_code": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"location_id": "",
|
||||||
|
"next_send_date": "",
|
||||||
|
"number": "",
|
||||||
|
"paid_to_date": 0,
|
||||||
|
"partial": 0,
|
||||||
|
"partial_due_date": "",
|
||||||
|
"po_number": "",
|
||||||
|
"private_notes": "",
|
||||||
|
"project_id": "",
|
||||||
|
"public_notes": "",
|
||||||
|
"reminder1_sent": "",
|
||||||
|
"reminder2_sent": "",
|
||||||
|
"reminder3_sent": "",
|
||||||
|
"reminder_last_sent": "",
|
||||||
|
"status_id": "",
|
||||||
|
"subscription_id": "",
|
||||||
|
"sync": null,
|
||||||
|
"tags": [],
|
||||||
|
"tax_info": {},
|
||||||
|
"tax_name1": "",
|
||||||
|
"tax_name2": "",
|
||||||
|
"tax_name3": "",
|
||||||
|
"tax_rate1": 0,
|
||||||
|
"tax_rate2": 0,
|
||||||
|
"tax_rate3": 0,
|
||||||
|
"terms": "",
|
||||||
|
"total_taxes": 0,
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"uses_inclusive_taxes": false,
|
||||||
|
"vendor_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+25
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"created_at": 0,
|
||||||
|
"design": {
|
||||||
|
"body": "",
|
||||||
|
"footer": "",
|
||||||
|
"header": "",
|
||||||
|
"includes": "",
|
||||||
|
"product": "",
|
||||||
|
"task": ""
|
||||||
|
},
|
||||||
|
"entities": "",
|
||||||
|
"id": "",
|
||||||
|
"is_active": false,
|
||||||
|
"is_custom": false,
|
||||||
|
"is_deleted": false,
|
||||||
|
"is_free": false,
|
||||||
|
"is_template": false,
|
||||||
|
"name": "",
|
||||||
|
"updated_at": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+29
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"disk": "",
|
||||||
|
"documentable_id": "",
|
||||||
|
"documentable_type": "",
|
||||||
|
"hash": "",
|
||||||
|
"height": 0,
|
||||||
|
"id": "",
|
||||||
|
"is_default": false,
|
||||||
|
"is_deleted": false,
|
||||||
|
"is_public": false,
|
||||||
|
"link": "",
|
||||||
|
"name": "",
|
||||||
|
"preview": "",
|
||||||
|
"project_id": "",
|
||||||
|
"size": 0,
|
||||||
|
"type": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"url": "",
|
||||||
|
"user_id": "",
|
||||||
|
"vendor_id": "",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"color": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"name": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+55
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"amount": 0,
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"bank_id": "",
|
||||||
|
"calculate_tax_by_amount": false,
|
||||||
|
"category_id": "",
|
||||||
|
"client_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"currency_id": "",
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"documents": [],
|
||||||
|
"e_invoice": {},
|
||||||
|
"entity_type": "",
|
||||||
|
"exchange_rate": 0,
|
||||||
|
"expense_currency_id": "",
|
||||||
|
"foreign_amount": 0,
|
||||||
|
"id": "",
|
||||||
|
"invoice_currency_id": "",
|
||||||
|
"invoice_documents": false,
|
||||||
|
"invoice_id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"number": "",
|
||||||
|
"payment_date": "",
|
||||||
|
"payment_type_id": "",
|
||||||
|
"private_notes": "",
|
||||||
|
"project_id": "",
|
||||||
|
"public_notes": "",
|
||||||
|
"recurring_expense_id": "",
|
||||||
|
"should_be_invoiced": false,
|
||||||
|
"tags": [],
|
||||||
|
"tax_amount1": 0,
|
||||||
|
"tax_amount2": 0,
|
||||||
|
"tax_amount3": 0,
|
||||||
|
"tax_name1": "",
|
||||||
|
"tax_name2": "",
|
||||||
|
"tax_name3": "",
|
||||||
|
"tax_rate1": 0,
|
||||||
|
"tax_rate2": 0,
|
||||||
|
"tax_rate3": 0,
|
||||||
|
"transaction_id": "",
|
||||||
|
"transaction_reference": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"uses_inclusive_taxes": false,
|
||||||
|
"vendor_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"created_at": 0,
|
||||||
|
"documents": [],
|
||||||
|
"id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"name": "",
|
||||||
|
"settings": {
|
||||||
|
"entity": ""
|
||||||
|
},
|
||||||
|
"updated_at": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+37
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"api_version": "",
|
||||||
|
"cache_enabled": false,
|
||||||
|
"env_writable": false,
|
||||||
|
"exchange_rate_api_not_configured": false,
|
||||||
|
"exec": false,
|
||||||
|
"extensions": [
|
||||||
|
{
|
||||||
|
"gd": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"file_permissions": "",
|
||||||
|
"flutter_renderer": "",
|
||||||
|
"is_docker": false,
|
||||||
|
"jobs_pending": 0,
|
||||||
|
"mail_mailer": "",
|
||||||
|
"open_basedir": false,
|
||||||
|
"pdf_engine": "",
|
||||||
|
"pending_migrations": false,
|
||||||
|
"phantom_enabled": false,
|
||||||
|
"php_version": {
|
||||||
|
"current_php_cli_version": "",
|
||||||
|
"current_php_version": "",
|
||||||
|
"is_okay": false,
|
||||||
|
"memory_limit": "",
|
||||||
|
"minimum_php_version": ""
|
||||||
|
},
|
||||||
|
"queue": "",
|
||||||
|
"queue_data": {
|
||||||
|
"failed": 0,
|
||||||
|
"last_error": "",
|
||||||
|
"pending": 0
|
||||||
|
},
|
||||||
|
"simple_db_check": false,
|
||||||
|
"system_health": false,
|
||||||
|
"trailing_slash": false
|
||||||
|
}
|
||||||
Vendored
+140
@@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"amount": 0,
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"auto_bill_enabled": false,
|
||||||
|
"backup": {
|
||||||
|
"adjustable_amount": 0,
|
||||||
|
"cancellation": {
|
||||||
|
"adjustment": 0,
|
||||||
|
"status_id": 0
|
||||||
|
},
|
||||||
|
"child_invoice_ids": [],
|
||||||
|
"document_type": null,
|
||||||
|
"e_invoice_cleared_at": null,
|
||||||
|
"e_invoice_status": null,
|
||||||
|
"guid": "",
|
||||||
|
"notes": null,
|
||||||
|
"parent_invoice_id": null,
|
||||||
|
"parent_invoice_number": null,
|
||||||
|
"redirect": null
|
||||||
|
},
|
||||||
|
"balance": 0,
|
||||||
|
"client_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"custom_surcharge1": 0,
|
||||||
|
"custom_surcharge2": 0,
|
||||||
|
"custom_surcharge3": 0,
|
||||||
|
"custom_surcharge4": 0,
|
||||||
|
"custom_surcharge_tax1": false,
|
||||||
|
"custom_surcharge_tax2": false,
|
||||||
|
"custom_surcharge_tax3": false,
|
||||||
|
"custom_surcharge_tax4": false,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"design_id": "",
|
||||||
|
"discount": 0,
|
||||||
|
"documents": [],
|
||||||
|
"due_date": "",
|
||||||
|
"e_invoice": {},
|
||||||
|
"entity_type": "",
|
||||||
|
"exchange_rate": 0,
|
||||||
|
"footer": "",
|
||||||
|
"has_expenses": false,
|
||||||
|
"has_tasks": false,
|
||||||
|
"id": "",
|
||||||
|
"invitations": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"can_sign": false,
|
||||||
|
"client_contact_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"email_error": "",
|
||||||
|
"email_status": "",
|
||||||
|
"id": "",
|
||||||
|
"key": "",
|
||||||
|
"link": "",
|
||||||
|
"message_id": "",
|
||||||
|
"opened_date": "",
|
||||||
|
"sent_date": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"viewed_date": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_amount_discount": false,
|
||||||
|
"is_deleted": false,
|
||||||
|
"last_sent_date": "",
|
||||||
|
"line_items": [
|
||||||
|
{
|
||||||
|
"cost": 0,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"discount": 0,
|
||||||
|
"expense_id": "",
|
||||||
|
"gross_line_total": 0,
|
||||||
|
"income_account_id": "",
|
||||||
|
"is_amount_discount": false,
|
||||||
|
"line_total": 0,
|
||||||
|
"net_cost": 0,
|
||||||
|
"notes": "",
|
||||||
|
"product_cost": 0,
|
||||||
|
"product_key": "",
|
||||||
|
"quantity": 0,
|
||||||
|
"sort_id": "",
|
||||||
|
"tags": "",
|
||||||
|
"task_id": "",
|
||||||
|
"tax_amount": 0,
|
||||||
|
"tax_id": "",
|
||||||
|
"tax_name1": "",
|
||||||
|
"tax_name2": "",
|
||||||
|
"tax_name3": "",
|
||||||
|
"tax_rate1": 0,
|
||||||
|
"tax_rate2": 0,
|
||||||
|
"tax_rate3": 0,
|
||||||
|
"type_id": "",
|
||||||
|
"unit_code": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"location_id": "",
|
||||||
|
"next_send_date": "",
|
||||||
|
"number": "",
|
||||||
|
"paid_to_date": 0,
|
||||||
|
"partial": 0,
|
||||||
|
"partial_due_date": "",
|
||||||
|
"po_number": "",
|
||||||
|
"private_notes": "",
|
||||||
|
"project_id": "",
|
||||||
|
"public_notes": "",
|
||||||
|
"recurring_id": "",
|
||||||
|
"reminder1_sent": "",
|
||||||
|
"reminder2_sent": "",
|
||||||
|
"reminder3_sent": "",
|
||||||
|
"reminder_last_sent": "",
|
||||||
|
"status_id": "",
|
||||||
|
"subscription_id": "",
|
||||||
|
"sync": null,
|
||||||
|
"tags": [],
|
||||||
|
"tax_info": {},
|
||||||
|
"tax_name1": "",
|
||||||
|
"tax_name2": "",
|
||||||
|
"tax_name3": "",
|
||||||
|
"tax_rate1": 0,
|
||||||
|
"tax_rate2": 0,
|
||||||
|
"tax_rate3": 0,
|
||||||
|
"terms": "",
|
||||||
|
"total_taxes": 0,
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"uses_inclusive_taxes": false,
|
||||||
|
"vendor_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+27
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"address1": "",
|
||||||
|
"address2": "",
|
||||||
|
"archived_at": 0,
|
||||||
|
"city": "",
|
||||||
|
"client_id": "",
|
||||||
|
"country_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"is_shipping_location": false,
|
||||||
|
"name": "",
|
||||||
|
"postal_code": "",
|
||||||
|
"state": "",
|
||||||
|
"tax_data": {},
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"vendor_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"created_at": 0,
|
||||||
|
"id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"name": "",
|
||||||
|
"num_days": 0,
|
||||||
|
"updated_at": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+52
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"amount": 0,
|
||||||
|
"applied": 0,
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"client_contact_id": "",
|
||||||
|
"client_id": "",
|
||||||
|
"company_gateway_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"currency_id": "",
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"documents": [],
|
||||||
|
"exchange_currency_id": "",
|
||||||
|
"exchange_rate": 0,
|
||||||
|
"gateway_type_id": "",
|
||||||
|
"id": "",
|
||||||
|
"invitation_id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"is_manual": false,
|
||||||
|
"number": "",
|
||||||
|
"paymentables": [
|
||||||
|
{
|
||||||
|
"amount": 0,
|
||||||
|
"archived_at": 0,
|
||||||
|
"created_at": 0,
|
||||||
|
"id": "",
|
||||||
|
"invoice_id": "",
|
||||||
|
"payment_id": "",
|
||||||
|
"refunded": 0,
|
||||||
|
"updated_at": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"private_notes": "",
|
||||||
|
"project_id": "",
|
||||||
|
"refunded": 0,
|
||||||
|
"status_id": "",
|
||||||
|
"tags": [],
|
||||||
|
"transaction_id": "",
|
||||||
|
"transaction_reference": "",
|
||||||
|
"type_id": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"vendor_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+37
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"cost": 0,
|
||||||
|
"created_at": 0,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"documents": [],
|
||||||
|
"id": "",
|
||||||
|
"in_stock_quantity": 0,
|
||||||
|
"income_account_id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"max_quantity": 0,
|
||||||
|
"notes": "",
|
||||||
|
"price": 0,
|
||||||
|
"product_image": "",
|
||||||
|
"product_key": "",
|
||||||
|
"quantity": 0,
|
||||||
|
"stock_notification": false,
|
||||||
|
"stock_notification_threshold": 0,
|
||||||
|
"tags": [],
|
||||||
|
"tax_id": "",
|
||||||
|
"tax_name1": "",
|
||||||
|
"tax_name2": "",
|
||||||
|
"tax_name3": "",
|
||||||
|
"tax_rate1": 0,
|
||||||
|
"tax_rate2": 0,
|
||||||
|
"tax_rate3": 0,
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+30
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"budgeted_amount": 0,
|
||||||
|
"budgeted_hours": 0,
|
||||||
|
"client_id": "",
|
||||||
|
"color": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"current_hours": 0,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"documents": [],
|
||||||
|
"due_date": "",
|
||||||
|
"id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"name": "",
|
||||||
|
"number": "",
|
||||||
|
"private_notes": "",
|
||||||
|
"public_notes": "",
|
||||||
|
"tags": [],
|
||||||
|
"task_rate": 0,
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+93
@@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"amount": 0,
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"balance": 0,
|
||||||
|
"client_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"currency_id": "",
|
||||||
|
"custom_surcharge1": 0,
|
||||||
|
"custom_surcharge2": 0,
|
||||||
|
"custom_surcharge3": 0,
|
||||||
|
"custom_surcharge4": 0,
|
||||||
|
"custom_surcharge_tax1": false,
|
||||||
|
"custom_surcharge_tax2": false,
|
||||||
|
"custom_surcharge_tax3": false,
|
||||||
|
"custom_surcharge_tax4": false,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"design_id": "",
|
||||||
|
"discount": 0,
|
||||||
|
"documents": [],
|
||||||
|
"due_date": "",
|
||||||
|
"e_invoice": {},
|
||||||
|
"entity_type": "",
|
||||||
|
"exchange_rate": 0,
|
||||||
|
"expense_id": "",
|
||||||
|
"footer": "",
|
||||||
|
"has_expenses": false,
|
||||||
|
"has_tasks": false,
|
||||||
|
"id": "",
|
||||||
|
"invitations": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"can_sign": false,
|
||||||
|
"created_at": 0,
|
||||||
|
"email_error": "",
|
||||||
|
"email_status": "",
|
||||||
|
"id": "",
|
||||||
|
"key": "",
|
||||||
|
"link": "",
|
||||||
|
"message_id": "",
|
||||||
|
"opened_date": "",
|
||||||
|
"sent_date": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"vendor_contact_id": "",
|
||||||
|
"viewed_date": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invoice_id": "",
|
||||||
|
"is_amount_discount": false,
|
||||||
|
"is_deleted": false,
|
||||||
|
"last_sent_date": "",
|
||||||
|
"line_items": [],
|
||||||
|
"location_id": "",
|
||||||
|
"next_send_date": "",
|
||||||
|
"number": "",
|
||||||
|
"paid_to_date": 0,
|
||||||
|
"partial": 0,
|
||||||
|
"partial_due_date": "",
|
||||||
|
"po_number": "",
|
||||||
|
"private_notes": "",
|
||||||
|
"project_id": "",
|
||||||
|
"public_notes": "",
|
||||||
|
"quote_id": "",
|
||||||
|
"reminder1_sent": "",
|
||||||
|
"reminder2_sent": "",
|
||||||
|
"reminder3_sent": "",
|
||||||
|
"reminder_last_sent": "",
|
||||||
|
"status_id": "",
|
||||||
|
"subscription_id": "",
|
||||||
|
"sync": null,
|
||||||
|
"tags": [],
|
||||||
|
"tax_info": {},
|
||||||
|
"tax_name1": "",
|
||||||
|
"tax_name2": "",
|
||||||
|
"tax_name3": "",
|
||||||
|
"tax_rate1": 0,
|
||||||
|
"tax_rate2": 0,
|
||||||
|
"tax_rate3": 0,
|
||||||
|
"terms": "",
|
||||||
|
"total_taxes": 0,
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"uses_inclusive_taxes": false,
|
||||||
|
"vendor_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+123
@@ -0,0 +1,123 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"amount": 0,
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"balance": 0,
|
||||||
|
"client_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"custom_surcharge1": 0,
|
||||||
|
"custom_surcharge2": 0,
|
||||||
|
"custom_surcharge3": 0,
|
||||||
|
"custom_surcharge4": 0,
|
||||||
|
"custom_surcharge_tax1": false,
|
||||||
|
"custom_surcharge_tax2": false,
|
||||||
|
"custom_surcharge_tax3": false,
|
||||||
|
"custom_surcharge_tax4": false,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"design_id": "",
|
||||||
|
"discount": 0,
|
||||||
|
"documents": [],
|
||||||
|
"due_date": "",
|
||||||
|
"e_invoice": {},
|
||||||
|
"entity_type": "",
|
||||||
|
"exchange_rate": 0,
|
||||||
|
"footer": "",
|
||||||
|
"has_expenses": false,
|
||||||
|
"has_tasks": false,
|
||||||
|
"id": "",
|
||||||
|
"invitations": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"can_sign": false,
|
||||||
|
"client_contact_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"email_error": "",
|
||||||
|
"email_status": "",
|
||||||
|
"id": "",
|
||||||
|
"key": "",
|
||||||
|
"link": "",
|
||||||
|
"message_id": "",
|
||||||
|
"opened_date": "",
|
||||||
|
"sent_date": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"viewed_date": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invoice_id": "",
|
||||||
|
"is_amount_discount": false,
|
||||||
|
"is_deleted": false,
|
||||||
|
"last_sent_date": "",
|
||||||
|
"line_items": [
|
||||||
|
{
|
||||||
|
"cost": 0,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"discount": 0,
|
||||||
|
"expense_id": "",
|
||||||
|
"gross_line_total": 0,
|
||||||
|
"income_account_id": "",
|
||||||
|
"is_amount_discount": false,
|
||||||
|
"line_total": 0,
|
||||||
|
"net_cost": 0,
|
||||||
|
"notes": "",
|
||||||
|
"product_cost": 0,
|
||||||
|
"product_key": "",
|
||||||
|
"quantity": 0,
|
||||||
|
"sort_id": "",
|
||||||
|
"tags": "",
|
||||||
|
"task_id": "",
|
||||||
|
"tax_amount": 0,
|
||||||
|
"tax_id": "",
|
||||||
|
"tax_name1": "",
|
||||||
|
"tax_name2": "",
|
||||||
|
"tax_name3": "",
|
||||||
|
"tax_rate1": 0,
|
||||||
|
"tax_rate2": 0,
|
||||||
|
"tax_rate3": 0,
|
||||||
|
"type_id": "",
|
||||||
|
"unit_code": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"location_id": "",
|
||||||
|
"next_send_date": "",
|
||||||
|
"number": "",
|
||||||
|
"paid_to_date": 0,
|
||||||
|
"partial": 0,
|
||||||
|
"partial_due_date": "",
|
||||||
|
"po_number": "",
|
||||||
|
"private_notes": "",
|
||||||
|
"project_id": "",
|
||||||
|
"public_notes": "",
|
||||||
|
"reminder1_sent": "",
|
||||||
|
"reminder2_sent": "",
|
||||||
|
"reminder3_sent": "",
|
||||||
|
"reminder_last_sent": "",
|
||||||
|
"status_id": "",
|
||||||
|
"subscription_id": "",
|
||||||
|
"sync": null,
|
||||||
|
"tags": [],
|
||||||
|
"tax_info": {},
|
||||||
|
"tax_name1": "",
|
||||||
|
"tax_name2": "",
|
||||||
|
"tax_name3": "",
|
||||||
|
"tax_rate1": 0,
|
||||||
|
"tax_rate2": 0,
|
||||||
|
"tax_rate3": 0,
|
||||||
|
"terms": "",
|
||||||
|
"total_taxes": 0,
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"uses_inclusive_taxes": false,
|
||||||
|
"vendor_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"amount": 0,
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"bank_id": "",
|
||||||
|
"calculate_tax_by_amount": false,
|
||||||
|
"category_id": "",
|
||||||
|
"client_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"currency_id": "",
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"documents": [],
|
||||||
|
"entity_type": "",
|
||||||
|
"exchange_rate": 0,
|
||||||
|
"foreign_amount": 0,
|
||||||
|
"frequency_id": "",
|
||||||
|
"id": "",
|
||||||
|
"invoice_currency_id": "",
|
||||||
|
"invoice_documents": false,
|
||||||
|
"invoice_id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"last_sent_date": "",
|
||||||
|
"next_send_date": "",
|
||||||
|
"number": "",
|
||||||
|
"payment_date": "",
|
||||||
|
"payment_type_id": "",
|
||||||
|
"private_notes": "",
|
||||||
|
"project_id": "",
|
||||||
|
"public_notes": "",
|
||||||
|
"recurring_dates": [],
|
||||||
|
"recurring_expense_currency_id": "",
|
||||||
|
"remaining_cycles": 0,
|
||||||
|
"should_be_invoiced": false,
|
||||||
|
"status_id": "",
|
||||||
|
"tags": [],
|
||||||
|
"tax_amount1": 0,
|
||||||
|
"tax_amount2": 0,
|
||||||
|
"tax_amount3": 0,
|
||||||
|
"tax_name1": "",
|
||||||
|
"tax_name2": "",
|
||||||
|
"tax_name3": "",
|
||||||
|
"tax_rate1": 0,
|
||||||
|
"tax_rate2": 0,
|
||||||
|
"tax_rate3": 0,
|
||||||
|
"transaction_id": "",
|
||||||
|
"transaction_reference": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"uses_inclusive_taxes": false,
|
||||||
|
"vendor_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"amount": 0,
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"auto_bill": "",
|
||||||
|
"auto_bill_enabled": false,
|
||||||
|
"balance": 0,
|
||||||
|
"client_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"custom_surcharge1": 0,
|
||||||
|
"custom_surcharge2": 0,
|
||||||
|
"custom_surcharge3": 0,
|
||||||
|
"custom_surcharge4": 0,
|
||||||
|
"custom_surcharge_tax1": false,
|
||||||
|
"custom_surcharge_tax2": false,
|
||||||
|
"custom_surcharge_tax3": false,
|
||||||
|
"custom_surcharge_tax4": false,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"design_id": "",
|
||||||
|
"discount": 0,
|
||||||
|
"documents": [],
|
||||||
|
"due_date": "",
|
||||||
|
"due_date_days": "",
|
||||||
|
"e_invoice": {},
|
||||||
|
"entity_type": "",
|
||||||
|
"exchange_rate": 0,
|
||||||
|
"footer": "",
|
||||||
|
"frequency_id": "",
|
||||||
|
"has_expenses": false,
|
||||||
|
"has_tasks": false,
|
||||||
|
"id": "",
|
||||||
|
"invitations": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"can_sign": false,
|
||||||
|
"client_contact_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"email_error": "",
|
||||||
|
"email_status": "",
|
||||||
|
"id": "",
|
||||||
|
"key": "",
|
||||||
|
"link": "",
|
||||||
|
"message_id": "",
|
||||||
|
"opened_date": "",
|
||||||
|
"sent_date": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"viewed_date": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_amount_discount": false,
|
||||||
|
"is_deleted": false,
|
||||||
|
"last_sent_date": "",
|
||||||
|
"line_items": [],
|
||||||
|
"location_id": "",
|
||||||
|
"next_send_date": "",
|
||||||
|
"next_send_datetime": "",
|
||||||
|
"number": "",
|
||||||
|
"paid_to_date": 0,
|
||||||
|
"partial": 0,
|
||||||
|
"partial_due_date": "",
|
||||||
|
"po_number": "",
|
||||||
|
"private_notes": "",
|
||||||
|
"project_id": "",
|
||||||
|
"public_notes": "",
|
||||||
|
"recurring_dates": [],
|
||||||
|
"remaining_cycles": 0,
|
||||||
|
"status_id": "",
|
||||||
|
"subscription_id": "",
|
||||||
|
"tags": [],
|
||||||
|
"tax_name1": "",
|
||||||
|
"tax_name2": "",
|
||||||
|
"tax_name3": "",
|
||||||
|
"tax_rate1": 0,
|
||||||
|
"tax_rate2": 0,
|
||||||
|
"tax_rate3": 0,
|
||||||
|
"terms": "",
|
||||||
|
"total_taxes": 0,
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"uses_inclusive_taxes": false,
|
||||||
|
"vendor_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+185
@@ -0,0 +1,185 @@
|
|||||||
|
{
|
||||||
|
"bulk_updates": {
|
||||||
|
"client": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"expense": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"recurring_invoice": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"task": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"countries": [
|
||||||
|
{
|
||||||
|
"capital": "",
|
||||||
|
"citizenship": "",
|
||||||
|
"country_code": "",
|
||||||
|
"currency": "",
|
||||||
|
"currency_code": "",
|
||||||
|
"currency_sub_unit": "",
|
||||||
|
"decimal_separator": "",
|
||||||
|
"eea": false,
|
||||||
|
"full_name": "",
|
||||||
|
"id": "",
|
||||||
|
"iso_3166_2": "",
|
||||||
|
"iso_3166_3": "",
|
||||||
|
"name": "",
|
||||||
|
"region_code": "",
|
||||||
|
"sub_region_code": "",
|
||||||
|
"swap_currency_symbol": false,
|
||||||
|
"swap_postal_code": false,
|
||||||
|
"thousand_separator": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"currencies": [
|
||||||
|
{
|
||||||
|
"code": "",
|
||||||
|
"decimal_separator": "",
|
||||||
|
"exchange_rate": 0,
|
||||||
|
"id": "",
|
||||||
|
"name": "",
|
||||||
|
"precision": 0,
|
||||||
|
"swap_currency_symbol": false,
|
||||||
|
"symbol": "",
|
||||||
|
"thousand_separator": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"date_formats": [
|
||||||
|
{
|
||||||
|
"format": "",
|
||||||
|
"format_dart": "",
|
||||||
|
"format_moment": "",
|
||||||
|
"id": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"datetime_formats": [
|
||||||
|
{
|
||||||
|
"format": "",
|
||||||
|
"format_dart": "",
|
||||||
|
"format_moment": "",
|
||||||
|
"id": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gateways": [
|
||||||
|
{
|
||||||
|
"created_at": 0,
|
||||||
|
"default_gateway_type_id": "",
|
||||||
|
"fields": "",
|
||||||
|
"id": "",
|
||||||
|
"is_offsite": false,
|
||||||
|
"is_secure": false,
|
||||||
|
"key": "",
|
||||||
|
"name": "",
|
||||||
|
"options": {
|
||||||
|
"1": {
|
||||||
|
"refund": false,
|
||||||
|
"token_billing": false,
|
||||||
|
"webhooks": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"refund": false,
|
||||||
|
"token_billing": false,
|
||||||
|
"webhooks": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"provider": "",
|
||||||
|
"site_url": "",
|
||||||
|
"sort_order": 0,
|
||||||
|
"updated_at": 0,
|
||||||
|
"visible": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"industries": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"locale": "",
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license_key": false,
|
||||||
|
"payment_types": [
|
||||||
|
{
|
||||||
|
"gateway_type_id": 0,
|
||||||
|
"id": "",
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sizes": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"templates": {
|
||||||
|
"invoice": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
},
|
||||||
|
"partial_payment": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
},
|
||||||
|
"payment": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
},
|
||||||
|
"payment_failed": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
},
|
||||||
|
"purchase_order": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
},
|
||||||
|
"quote": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
},
|
||||||
|
"quote_reminder1": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
},
|
||||||
|
"reminder1": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
},
|
||||||
|
"reminder2": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
},
|
||||||
|
"reminder3": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
},
|
||||||
|
"reminder_endless": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
},
|
||||||
|
"statement": {
|
||||||
|
"body": "",
|
||||||
|
"subject": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timezones": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"location": "",
|
||||||
|
"name": "",
|
||||||
|
"utc_offset": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"App\\Livewire\\BillingPortal\\Authentication\\Login": {
|
||||||
|
"dependencies": [],
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"App\\Livewire\\BillingPortal\\Authentication\\Register": {
|
||||||
|
"dependencies": [],
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"App\\Livewire\\BillingPortal\\Authentication\\RegisterOrLogin": {
|
||||||
|
"dependencies": [],
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"App\\Livewire\\BillingPortal\\Cart\\Cart": {
|
||||||
|
"dependencies": [],
|
||||||
|
"id": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"category_id": 0,
|
||||||
|
"client_id": "",
|
||||||
|
"company_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"event_id": 0,
|
||||||
|
"id": "",
|
||||||
|
"log": "",
|
||||||
|
"type_id": 0,
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"color": null,
|
||||||
|
"created_at": 0,
|
||||||
|
"entity_type": "",
|
||||||
|
"id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"name": "",
|
||||||
|
"updated_at": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"color": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"name": "",
|
||||||
|
"status_order": 0,
|
||||||
|
"updated_at": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+36
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"client_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"date": "",
|
||||||
|
"description": "",
|
||||||
|
"documents": [],
|
||||||
|
"due_date": "",
|
||||||
|
"duration": 0,
|
||||||
|
"estimated_duration": null,
|
||||||
|
"id": "",
|
||||||
|
"invoice_id": "",
|
||||||
|
"is_date_based": false,
|
||||||
|
"is_deleted": false,
|
||||||
|
"is_running": false,
|
||||||
|
"meta": {},
|
||||||
|
"number": "",
|
||||||
|
"project_id": "",
|
||||||
|
"rate": 0,
|
||||||
|
"status_id": "",
|
||||||
|
"status_order": null,
|
||||||
|
"status_sort_order": 0,
|
||||||
|
"tags": [],
|
||||||
|
"time_log": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"created_at": 0,
|
||||||
|
"id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"name": "",
|
||||||
|
"rate": 0,
|
||||||
|
"updated_at": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"created_at": 0,
|
||||||
|
"id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"is_system": false,
|
||||||
|
"name": "",
|
||||||
|
"token": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+44
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"created_at": 0,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"email": "",
|
||||||
|
"email_verified_at": 0,
|
||||||
|
"first_name": "",
|
||||||
|
"google_2fa_secret": false,
|
||||||
|
"has_password": false,
|
||||||
|
"id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"language_id": "",
|
||||||
|
"last_confirmed_email_address": "",
|
||||||
|
"last_login": 0,
|
||||||
|
"last_name": "",
|
||||||
|
"oauth_provider_id": "",
|
||||||
|
"oauth_user_token": "",
|
||||||
|
"passkey_count": 0,
|
||||||
|
"passkey_enabled": false,
|
||||||
|
"phone": "",
|
||||||
|
"referral_code": "",
|
||||||
|
"referral_meta": {
|
||||||
|
"enterprise": 0,
|
||||||
|
"free": 0,
|
||||||
|
"pro": 0
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"calendar_connection": {
|
||||||
|
"email": "",
|
||||||
|
"status": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"signature": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_logged_in_notification": false,
|
||||||
|
"verified_phone_number": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+63
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"address1": "",
|
||||||
|
"address2": "",
|
||||||
|
"archived_at": 0,
|
||||||
|
"assigned_user_id": "",
|
||||||
|
"city": "",
|
||||||
|
"classification": "",
|
||||||
|
"contacts": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"can_sign": false,
|
||||||
|
"cc_only": false,
|
||||||
|
"created_at": 0,
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"email": "",
|
||||||
|
"first_name": "",
|
||||||
|
"id": "",
|
||||||
|
"is_primary": false,
|
||||||
|
"last_login": 0,
|
||||||
|
"last_name": "",
|
||||||
|
"link": "",
|
||||||
|
"password": "",
|
||||||
|
"phone": "",
|
||||||
|
"send_email": false,
|
||||||
|
"updated_at": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"country_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"currency_id": "",
|
||||||
|
"custom_value1": "",
|
||||||
|
"custom_value2": "",
|
||||||
|
"custom_value3": "",
|
||||||
|
"custom_value4": "",
|
||||||
|
"display_name": "",
|
||||||
|
"documents": [],
|
||||||
|
"id": "",
|
||||||
|
"id_number": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"is_tax_exempt": false,
|
||||||
|
"language_id": "",
|
||||||
|
"last_login": 0,
|
||||||
|
"name": "",
|
||||||
|
"number": "",
|
||||||
|
"phone": "",
|
||||||
|
"postal_code": "",
|
||||||
|
"private_notes": "",
|
||||||
|
"public_notes": "",
|
||||||
|
"routing_id": "",
|
||||||
|
"state": "",
|
||||||
|
"tags": [],
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": "",
|
||||||
|
"vat_number": "",
|
||||||
|
"website": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+18
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"archived_at": 0,
|
||||||
|
"company_id": "",
|
||||||
|
"created_at": 0,
|
||||||
|
"event_id": "",
|
||||||
|
"format": "",
|
||||||
|
"headers": [],
|
||||||
|
"id": "",
|
||||||
|
"is_deleted": false,
|
||||||
|
"rest_method": "",
|
||||||
|
"target_url": "",
|
||||||
|
"updated_at": 0,
|
||||||
|
"user_id": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
//! Decodes fixtures captured from a real server.
|
||||||
|
//!
|
||||||
|
//! `tests/fixtures/*.json` are written by `examples/capture_shapes.rs` and
|
||||||
|
//! hold only key names and JSON types (strings are `""`, numbers `0`, bools
|
||||||
|
//! `false`). Mock responses written from the upstream source have been wrong
|
||||||
|
//! about the real thing before, so these pin the shapes a server actually
|
||||||
|
//! sent. To refresh them, seed a disposable instance with
|
||||||
|
//! `examples/seed_sample_data.rs` and re-run the capture.
|
||||||
|
//!
|
||||||
|
//! Three properties are checked: every fixture decodes into its model
|
||||||
|
//! (goal 2), a decode followed by a re-serialise loses no key (goal 1), and no
|
||||||
|
//! fixture contains a real value, so a careless capture cannot leak data.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use invoiceninjars::models::*;
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
fn dir() -> PathBuf {
|
||||||
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(name: &str) -> Value {
|
||||||
|
let path = dir().join(format!("{name}.json"));
|
||||||
|
let text = fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
|
||||||
|
serde_json::from_str(&text).unwrap_or_else(|e| panic!("{}: {e}", path.display()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The captured row of a list fixture.
|
||||||
|
fn row(name: &str) -> Value {
|
||||||
|
load(name)["data"][0].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Key paths present in `raw` but absent from `out`. Arrays are compared
|
||||||
|
/// through their first element, which is all a fixture keeps.
|
||||||
|
fn lost_keys(raw: &Value, out: &Value, prefix: &str, into: &mut Vec<String>) {
|
||||||
|
match (raw, out) {
|
||||||
|
(Value::Object(raw), Value::Object(out)) => {
|
||||||
|
for (key, raw_value) in raw {
|
||||||
|
match out.get(key) {
|
||||||
|
Some(out_value) => {
|
||||||
|
lost_keys(raw_value, out_value, &format!("{prefix}.{key}"), into)
|
||||||
|
}
|
||||||
|
None => into.push(format!("{prefix}.{key}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Array(raw), Value::Array(out)) => {
|
||||||
|
if let (Some(raw), Some(out)) = (raw.first(), out.first()) {
|
||||||
|
lost_keys(raw, out, &format!("{prefix}[]"), into);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes `raw` as `T`, re-serialises it, and returns problems.
|
||||||
|
fn check<T: DeserializeOwned + Serialize>(name: &str, raw: &Value) -> Vec<String> {
|
||||||
|
let typed: T = match serde_json::from_value(raw.clone()) {
|
||||||
|
Ok(typed) => typed,
|
||||||
|
Err(e) => return vec![format!("{name}: does not decode: {e}")],
|
||||||
|
};
|
||||||
|
let out = serde_json::to_value(&typed).expect("serialises");
|
||||||
|
let mut lost = Vec::new();
|
||||||
|
lost_keys(raw, &out, name, &mut lost);
|
||||||
|
lost.into_iter()
|
||||||
|
.filter(|path| !OMITTED_WHEN_EMPTY.contains(&path.as_str()))
|
||||||
|
.map(|path| format!("{path}: key lost on a decode/re-serialise"))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fields that are deliberately left out of a request when empty, because the
|
||||||
|
/// server rejects the empty value (`""` becomes `NULL` on a non-null column,
|
||||||
|
/// or fails an `integer` rule). A fixture holds `""` placeholders, so these
|
||||||
|
/// show up as absent after a re-serialise. Adding to this list is a decision.
|
||||||
|
const OMITTED_WHEN_EMPTY: &[&str] = &[
|
||||||
|
"locations.country_id",
|
||||||
|
"purchase_orders.status_id",
|
||||||
|
"webhooks.format",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// One fixture per `(file, type)`; the macro also records which files it
|
||||||
|
/// covered so a new capture cannot sit untested.
|
||||||
|
macro_rules! list_fixtures {
|
||||||
|
($($file:literal => $ty:ty),+ $(,)?) => {
|
||||||
|
const COVERED_LISTS: &[&str] = &[$($file),+];
|
||||||
|
|
||||||
|
fn check_lists() -> Vec<String> {
|
||||||
|
let mut problems = Vec::new();
|
||||||
|
$( problems.extend(check::<$ty>($file, &row($file))); )+
|
||||||
|
problems
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
list_fixtures! {
|
||||||
|
"activities" => Activity,
|
||||||
|
"clients" => Client,
|
||||||
|
"companies" => Company,
|
||||||
|
"credits" => Credit,
|
||||||
|
"designs" => Design,
|
||||||
|
"documents" => Document,
|
||||||
|
"expense_categories" => ExpenseCategory,
|
||||||
|
"expenses" => Expense,
|
||||||
|
"group_settings" => GroupSetting,
|
||||||
|
"invoices" => Invoice,
|
||||||
|
"locations" => Location,
|
||||||
|
"payment_terms" => PaymentTerm,
|
||||||
|
"payments" => Payment,
|
||||||
|
"products" => Product,
|
||||||
|
"projects" => Project,
|
||||||
|
"purchase_orders" => PurchaseOrder,
|
||||||
|
"quotes" => Quote,
|
||||||
|
"recurring_expenses" => RecurringExpense,
|
||||||
|
"recurring_invoices" => RecurringInvoice,
|
||||||
|
"system_logs" => SystemLog,
|
||||||
|
"tags" => Tag,
|
||||||
|
"task_statuses" => TaskStatus,
|
||||||
|
"tasks" => Task,
|
||||||
|
"tax_rates" => TaxRate,
|
||||||
|
"tokens" => CompanyToken,
|
||||||
|
"users" => User,
|
||||||
|
"vendors" => Vendor,
|
||||||
|
"webhooks" => Webhook,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fixtures that are not a one-row list.
|
||||||
|
const COVERED_OTHER: &[&str] = &[
|
||||||
|
"statics",
|
||||||
|
"health_check",
|
||||||
|
"subscription_steps",
|
||||||
|
"activity_feed",
|
||||||
|
];
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_real_row_decodes_and_loses_no_key() {
|
||||||
|
let problems = check_lists();
|
||||||
|
assert!(
|
||||||
|
problems.is_empty(),
|
||||||
|
"{} problem(s):\n{}",
|
||||||
|
problems.len(),
|
||||||
|
problems.join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bare_endpoint_fixtures_decode() {
|
||||||
|
let mut problems = Vec::new();
|
||||||
|
problems.extend(check::<Statics>("statics", &load("statics")));
|
||||||
|
problems.extend(check::<HealthCheck>("health_check", &load("health_check")));
|
||||||
|
problems.extend(check::<ActivityFeedEntry>(
|
||||||
|
"activity_feed",
|
||||||
|
&load("activity_feed")["data"][0],
|
||||||
|
));
|
||||||
|
|
||||||
|
// Keyed by class name on a real server; see `subscription_steps`.
|
||||||
|
let steps: Result<BTreeMap<String, SubscriptionStep>, _> =
|
||||||
|
serde_json::from_value(load("subscription_steps"));
|
||||||
|
match steps {
|
||||||
|
Ok(steps) => assert!(!steps.is_empty(), "the captured steps object is empty"),
|
||||||
|
Err(e) => problems.push(format!("subscription_steps: {e}")),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(problems.is_empty(), "{}", problems.join("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_fixture_file_is_covered_by_a_test() {
|
||||||
|
let mut on_disk: Vec<String> = fs::read_dir(dir())
|
||||||
|
.expect("tests/fixtures exists")
|
||||||
|
.filter_map(|entry| {
|
||||||
|
let path = entry.ok()?.path();
|
||||||
|
(path.extension()? == "json").then(|| path.file_stem()?.to_str().map(String::from))?
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
on_disk.sort();
|
||||||
|
|
||||||
|
let mut covered: Vec<String> = COVERED_LISTS
|
||||||
|
.iter()
|
||||||
|
.chain(COVERED_OTHER)
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect();
|
||||||
|
covered.sort();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
on_disk, covered,
|
||||||
|
"a captured fixture has no test (or a test has no fixture)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The capture tool replaces every value; this refuses to pass if any
|
||||||
|
/// survived, so real data cannot be committed by accident.
|
||||||
|
#[test]
|
||||||
|
fn fixtures_hold_shapes_and_never_values() {
|
||||||
|
fn walk(value: &Value, at: &str, bad: &mut Vec<String>) {
|
||||||
|
match value {
|
||||||
|
Value::String(s) if !s.is_empty() => bad.push(format!("{at}: string value")),
|
||||||
|
Value::Number(n) if n.as_f64() != Some(0.0) => bad.push(format!("{at}: number value")),
|
||||||
|
Value::Bool(true) => bad.push(format!("{at}: true")),
|
||||||
|
Value::Array(items) => {
|
||||||
|
assert!(items.len() <= 1, "{at}: a fixture keeps one array element");
|
||||||
|
items.iter().for_each(|v| walk(v, &format!("{at}[]"), bad));
|
||||||
|
}
|
||||||
|
Value::Object(map) => map
|
||||||
|
.iter()
|
||||||
|
.for_each(|(k, v)| walk(v, &format!("{at}.{k}"), bad)),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bad = Vec::new();
|
||||||
|
for entry in fs::read_dir(dir()).expect("tests/fixtures exists") {
|
||||||
|
let path = entry.unwrap().path();
|
||||||
|
if path.extension().is_some_and(|e| e == "json") {
|
||||||
|
let name = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||||
|
walk(&load(&name), &name, &mut bad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
bad.is_empty(),
|
||||||
|
"fixtures contain values:\n{}",
|
||||||
|
bad.join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user