makea-cli 0.1.21__tar.gz → 0.1.22__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {makea_cli-0.1.21 → makea_cli-0.1.22}/PKG-INFO +3 -2
- {makea_cli-0.1.21 → makea_cli-0.1.22}/README.md +2 -1
- makea_cli-0.1.22/makea_cli/__init__.py +1 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/api_client.py +21 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/supplier/cmd.py +464 -57
- {makea_cli-0.1.21 → makea_cli-0.1.22}/pyproject.toml +1 -1
- makea_cli-0.1.21/makea_cli/__init__.py +0 -1
- {makea_cli-0.1.21 → makea_cli-0.1.22}/.claude/skills/create-supplier-profile/SKILL.md +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/.claude/skills/lark-cli-setup/SKILL.md +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/.claude/skills/link-supplier-user/SKILL.md +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/.claude/skills/makea-cli-setup/SKILL.md +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/.claude/skills/payment-revenue-report/SKILL.md +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/.claude/skills/supplier-user-id-migration/SKILL.md +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/.gitignore +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea-collection-upload/SKILL.md +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea-collection-upload/scripts/create_products.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/__main__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/auth_pkce.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/_util.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/analytics/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/analytics/cmd.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/auth/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/auth/cmd.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/designer/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/designer/cmd.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/directory/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/directory/cmd.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/document/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/document/cmd.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/email/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/email/cmd.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/library/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/library/cmd.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/metrics/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/metrics/cmd.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/orders/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/orders/cmd.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/product/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/product/cmd.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/production/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/production/cmd.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/production/pricing_spec.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/commands/supplier/__init__.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/config.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/main.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/makea_cli/posthog_client.py +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/npm/makea-cli/README.md +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/npm/makea-cli/makea-cli.mjs +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/npm/makea-cli/package.json +0 -0
- {makea_cli-0.1.21 → makea_cli-0.1.22}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: makea-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.22
|
|
4
4
|
Summary: Makea admin CLI (browser login + Makea admin HTTP API)
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: httpx>=0.27
|
|
@@ -82,7 +82,8 @@ makea-cli auth --logout
|
|
|
82
82
|
- `makea-cli create-quote-request -s <supplier_id> -p <product_reference_id> [--quote-type SAMPLING|PRODUCTION] [--message ...] [--due-date YYYY-MM-DD]` — 创建并发送 quote request(POST `/admin/supplier/create_quote_request`)
|
|
83
83
|
- `makea-cli get-quote-requests-by-supplier-id <supplier_id> [--status <status>] [-n <limit>]` — 直连后端按 supplier_id 查询 quote requests
|
|
84
84
|
- `makea-cli get-supplier-links-by-supplier-id <supplier_id> [--link-type SAMPLING|PRODUCTION]` — 直连后端按 supplier_id 查询 supplier links
|
|
85
|
-
- `makea-cli submit-supplier-quote <quote_request_id> --link-name ... --currency ... --sampling-price-
|
|
85
|
+
- `makea-cli submit-supplier-quote <quote_request_id> --link-name ... --currency ... --sampling-price-value ... --sampling-price-valid-until ... --sample-lead-time ... --deliverables-json ... --customer-note ...` — 以 admin 身份代供应商向指定 quote request 新增 quote。大货阶梯价(`--production-price-tiers-json`)和 `--lead-time` 现在是可选的;样衣加做费、MOQ 口径、寄料地址(CMT / full package 打样报价必填)、可选尺码颜色、规格行、typed link 等字段见 `--help`。pricing groups 提交时给不进去,提交完用 `update-supplier-quote` 补
|
|
86
|
+
- `makea-cli update-supplier-quote <product_id> <link_id> [--sampling-price-value ... --sampling-price-valid-until ...] [--production-price-tiers-json ...] [--pricing-groups-json ...] [...]` — 改一条已提交的 quote(supplier link):只发你传的字段,没传的后端不动;pricing groups(多套阶梯价)只有这条命令能写(POST `/admin/product/edit_supplier_link_override_info`)
|
|
86
87
|
- `makea-cli list-product-orders` — 列出所有设计师产品订单(GET `/admin/designer/get_all_product_orders`)
|
|
87
88
|
- `makea-cli list-sampling-orders-by-product <id>` — 按产品 reference id 列打样单
|
|
88
89
|
- `makea-cli list-production-orders-by-product <id>` — 按产品 reference id 列大货单
|
|
@@ -71,7 +71,8 @@ makea-cli auth --logout
|
|
|
71
71
|
- `makea-cli create-quote-request -s <supplier_id> -p <product_reference_id> [--quote-type SAMPLING|PRODUCTION] [--message ...] [--due-date YYYY-MM-DD]` — 创建并发送 quote request(POST `/admin/supplier/create_quote_request`)
|
|
72
72
|
- `makea-cli get-quote-requests-by-supplier-id <supplier_id> [--status <status>] [-n <limit>]` — 直连后端按 supplier_id 查询 quote requests
|
|
73
73
|
- `makea-cli get-supplier-links-by-supplier-id <supplier_id> [--link-type SAMPLING|PRODUCTION]` — 直连后端按 supplier_id 查询 supplier links
|
|
74
|
-
- `makea-cli submit-supplier-quote <quote_request_id> --link-name ... --currency ... --sampling-price-
|
|
74
|
+
- `makea-cli submit-supplier-quote <quote_request_id> --link-name ... --currency ... --sampling-price-value ... --sampling-price-valid-until ... --sample-lead-time ... --deliverables-json ... --customer-note ...` — 以 admin 身份代供应商向指定 quote request 新增 quote。大货阶梯价(`--production-price-tiers-json`)和 `--lead-time` 现在是可选的;样衣加做费、MOQ 口径、寄料地址(CMT / full package 打样报价必填)、可选尺码颜色、规格行、typed link 等字段见 `--help`。pricing groups 提交时给不进去,提交完用 `update-supplier-quote` 补
|
|
75
|
+
- `makea-cli update-supplier-quote <product_id> <link_id> [--sampling-price-value ... --sampling-price-valid-until ...] [--production-price-tiers-json ...] [--pricing-groups-json ...] [...]` — 改一条已提交的 quote(supplier link):只发你传的字段,没传的后端不动;pricing groups(多套阶梯价)只有这条命令能写(POST `/admin/product/edit_supplier_link_override_info`)
|
|
75
76
|
- `makea-cli list-product-orders` — 列出所有设计师产品订单(GET `/admin/designer/get_all_product_orders`)
|
|
76
77
|
- `makea-cli list-sampling-orders-by-product <id>` — 按产品 reference id 列打样单
|
|
77
78
|
- `makea-cli list-production-orders-by-product <id>` — 按产品 reference id 列大货单
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.22"
|
|
@@ -390,6 +390,27 @@ def submit_admin_quote_for_quote_request(
|
|
|
390
390
|
return post_json(f"/admin/quote_requests/{quote_request_id}/submit_quote", body)
|
|
391
391
|
|
|
392
392
|
|
|
393
|
+
def edit_supplier_link_override_info(
|
|
394
|
+
*,
|
|
395
|
+
product_id: str,
|
|
396
|
+
link_id: str,
|
|
397
|
+
override_info: dict[str, Any],
|
|
398
|
+
) -> Any:
|
|
399
|
+
"""
|
|
400
|
+
POST /admin/product/edit_supplier_link_override_info — admin updates an existing
|
|
401
|
+
supplier link (a submitted quote) in place. Only the keys present in
|
|
402
|
+
``override_info`` are applied; the rest of the link is left alone.
|
|
403
|
+
"""
|
|
404
|
+
return post_json(
|
|
405
|
+
"/admin/product/edit_supplier_link_override_info",
|
|
406
|
+
{
|
|
407
|
+
"product_id": product_id,
|
|
408
|
+
"link_id": link_id,
|
|
409
|
+
"override_info": override_info,
|
|
410
|
+
},
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
|
|
393
414
|
def upload_admin_link_document(
|
|
394
415
|
*,
|
|
395
416
|
file_path: Path,
|
|
@@ -20,6 +20,52 @@ def _normalize_add_supplier_body(raw: dict) -> dict:
|
|
|
20
20
|
return {"supplier": raw}
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
MOQ_APPLIES_TO_VALUES = ("style", "size", "color")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _parse_json_option(raw: str, option_name: str) -> object:
|
|
27
|
+
"""Parse a JSON-valued option, failing with the option's own name."""
|
|
28
|
+
try:
|
|
29
|
+
return json.loads(raw)
|
|
30
|
+
except json.JSONDecodeError as e:
|
|
31
|
+
console.print(f"[red]{option_name} must be valid JSON: {e}[/red]")
|
|
32
|
+
raise typer.Exit(1) from e
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _parse_json_list_option(raw: str, option_name: str) -> list:
|
|
36
|
+
parsed = _parse_json_option(raw, option_name)
|
|
37
|
+
if not isinstance(parsed, list):
|
|
38
|
+
console.print(f"[red]{option_name} must be a JSON array (list).[/red]")
|
|
39
|
+
raise typer.Exit(1)
|
|
40
|
+
return parsed
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _parse_json_object_option(raw: str, option_name: str) -> dict:
|
|
44
|
+
parsed = _parse_json_option(raw, option_name)
|
|
45
|
+
if not isinstance(parsed, dict):
|
|
46
|
+
console.print(f"[red]{option_name} must be a JSON object.[/red]")
|
|
47
|
+
raise typer.Exit(1)
|
|
48
|
+
return parsed
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _validate_moq_applies_to(values: list[str]) -> list[str]:
|
|
52
|
+
"""Normalize --moq-applies-to; the backend silently drops unknown values."""
|
|
53
|
+
normalized = [str(v).strip().lower() for v in values if str(v).strip()]
|
|
54
|
+
unknown = [v for v in normalized if v not in MOQ_APPLIES_TO_VALUES]
|
|
55
|
+
if unknown:
|
|
56
|
+
console.print(
|
|
57
|
+
f"[red]--moq-applies-to accepts only {', '.join(MOQ_APPLIES_TO_VALUES)} "
|
|
58
|
+
f"(got: {', '.join(unknown)}).[/red]"
|
|
59
|
+
)
|
|
60
|
+
raise typer.Exit(1)
|
|
61
|
+
return [v for v in MOQ_APPLIES_TO_VALUES if v in set(normalized)]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _none_if_null_literal(value: str) -> str | None:
|
|
65
|
+
"""Let an option clear a stored value by passing the literal `null`."""
|
|
66
|
+
return None if value.strip().lower() in ("null", "none") else value
|
|
67
|
+
|
|
68
|
+
|
|
23
69
|
def register(app: typer.Typer) -> None:
|
|
24
70
|
@app.command(
|
|
25
71
|
"list-suppliers",
|
|
@@ -472,7 +518,9 @@ def register(app: typer.Typer) -> None:
|
|
|
472
518
|
"submit-supplier-quote",
|
|
473
519
|
help=(
|
|
474
520
|
"POST /admin/quote_requests/{quote_request_id}/submit_quote — "
|
|
475
|
-
"admin submits a new quote (link) to a quote request."
|
|
521
|
+
"admin submits a new quote (link) to a quote request. "
|
|
522
|
+
"Pricing groups cannot be sent here (the submit endpoint ignores them) — "
|
|
523
|
+
"set them afterwards with `update-supplier-quote`."
|
|
476
524
|
),
|
|
477
525
|
)
|
|
478
526
|
def submit_supplier_quote(
|
|
@@ -490,10 +538,14 @@ def register(app: typer.Typer) -> None:
|
|
|
490
538
|
"--currency",
|
|
491
539
|
help="currency: quote currency (used by sampling price and fees).",
|
|
492
540
|
),
|
|
493
|
-
|
|
541
|
+
sampling_price_value: float = typer.Option(
|
|
494
542
|
...,
|
|
543
|
+
"--sampling-price-value",
|
|
495
544
|
"--sampling-price-amount",
|
|
496
|
-
help=
|
|
545
|
+
help=(
|
|
546
|
+
"sampling_price.value: sampling price (required, number). "
|
|
547
|
+
"Sent as `value` — the backend reads no other key."
|
|
548
|
+
),
|
|
497
549
|
),
|
|
498
550
|
sampling_price_valid_until: str = typer.Option(
|
|
499
551
|
...,
|
|
@@ -503,25 +555,11 @@ def register(app: typer.Typer) -> None:
|
|
|
503
555
|
"(required, ISO datetime string, e.g. 2026-05-01T00:00:00+00:00)."
|
|
504
556
|
),
|
|
505
557
|
),
|
|
506
|
-
production_price_tiers_json: str = typer.Option(
|
|
507
|
-
...,
|
|
508
|
-
"--production-price-tiers-json",
|
|
509
|
-
help=(
|
|
510
|
-
"production_price_tiers: tiered production price array "
|
|
511
|
-
"(required, JSON string). "
|
|
512
|
-
'Example: \'[{"min_qty":100,"max_qty":499,"unit_price":12.5}]\''
|
|
513
|
-
),
|
|
514
|
-
),
|
|
515
558
|
sample_lead_time: str = typer.Option(
|
|
516
559
|
...,
|
|
517
560
|
"--sample-lead-time",
|
|
518
561
|
help="sample_lead_time: sampling lead time (required, string).",
|
|
519
562
|
),
|
|
520
|
-
lead_time: str = typer.Option(
|
|
521
|
-
...,
|
|
522
|
-
"--lead-time",
|
|
523
|
-
help="lead_time: production lead time (required, string).",
|
|
524
|
-
),
|
|
525
563
|
deliverables_json: str = typer.Option(
|
|
526
564
|
...,
|
|
527
565
|
"--deliverables-json",
|
|
@@ -535,6 +573,28 @@ def register(app: typer.Typer) -> None:
|
|
|
535
573
|
"--customer-note",
|
|
536
574
|
help="customer_note: note for customer/platform (required, string; empty string allowed).",
|
|
537
575
|
),
|
|
576
|
+
production_price_tiers_json: str | None = typer.Option(
|
|
577
|
+
None,
|
|
578
|
+
"--production-price-tiers-json",
|
|
579
|
+
help=(
|
|
580
|
+
"production_price_tiers: tiered production price array "
|
|
581
|
+
"(optional since TECH-93 — a factory may quote the sample only). "
|
|
582
|
+
'Example: \'[{"min_qty":100,"max_qty":499,"unit_price":12.5}]\''
|
|
583
|
+
),
|
|
584
|
+
),
|
|
585
|
+
lead_time: str | None = typer.Option(
|
|
586
|
+
None,
|
|
587
|
+
"--lead-time",
|
|
588
|
+
help="lead_time: production lead time (optional since TECH-93, string).",
|
|
589
|
+
),
|
|
590
|
+
extra_sample_price_value: float | None = typer.Option(
|
|
591
|
+
None,
|
|
592
|
+
"--extra-sample-price-value",
|
|
593
|
+
help=(
|
|
594
|
+
"extra_sample_price.value: per-unit charge for extra samples "
|
|
595
|
+
"(optional, number; currency defaults to --currency)."
|
|
596
|
+
),
|
|
597
|
+
),
|
|
538
598
|
link_id: str | None = typer.Option(
|
|
539
599
|
None,
|
|
540
600
|
"--link-id",
|
|
@@ -550,6 +610,14 @@ def register(app: typer.Typer) -> None:
|
|
|
550
610
|
"--moq",
|
|
551
611
|
help="moq: minimum order quantity (optional, integer).",
|
|
552
612
|
),
|
|
613
|
+
moq_applies_to: list[str] | None = typer.Option(
|
|
614
|
+
None,
|
|
615
|
+
"--moq-applies-to",
|
|
616
|
+
help=(
|
|
617
|
+
"moq_applies_to: what the MOQ counts (optional, repeatable): "
|
|
618
|
+
"style, size, color. Empty means the supplier did not say."
|
|
619
|
+
),
|
|
620
|
+
),
|
|
553
621
|
production_price_incoterm: str | None = typer.Option(
|
|
554
622
|
None,
|
|
555
623
|
"--production-price-incoterm",
|
|
@@ -560,6 +628,63 @@ def register(app: typer.Typer) -> None:
|
|
|
560
628
|
"--production-payment-term",
|
|
561
629
|
help='production_payment_term: production payment term/deposit ratio (optional, e.g. "40" or "40%").',
|
|
562
630
|
),
|
|
631
|
+
material_ship_to_json: str | None = typer.Option(
|
|
632
|
+
None,
|
|
633
|
+
"--material-ship-to-json",
|
|
634
|
+
help=(
|
|
635
|
+
"material_ship_to_address: where the buyer ships materials "
|
|
636
|
+
"(JSON object with street, city, state, postal_code, country). "
|
|
637
|
+
"REQUIRED by the backend for a CMT or full-package SAMPLING quote — "
|
|
638
|
+
"street, city and country must be non-empty. "
|
|
639
|
+
'Example: \'{"street":"1 Test Rd","city":"Guangzhou","country":"CN"}\''
|
|
640
|
+
),
|
|
641
|
+
),
|
|
642
|
+
available_sizes_json: str | None = typer.Option(
|
|
643
|
+
None,
|
|
644
|
+
"--available-sizes-json",
|
|
645
|
+
help=(
|
|
646
|
+
"available_sizes: sizes the piece comes in (optional, JSON array of strings). "
|
|
647
|
+
'Example: \'["S","M","L"]\''
|
|
648
|
+
),
|
|
649
|
+
),
|
|
650
|
+
available_colors_json: str | None = typer.Option(
|
|
651
|
+
None,
|
|
652
|
+
"--available-colors-json",
|
|
653
|
+
help=(
|
|
654
|
+
"available_colors: colours the piece comes in (optional, JSON array of objects). "
|
|
655
|
+
'Example: \'[{"color_name":"Black","hex_code":"#000000","pantone_code":"Black 6 C"}]\''
|
|
656
|
+
),
|
|
657
|
+
),
|
|
658
|
+
size_chart_document_id: str | None = typer.Option(
|
|
659
|
+
None,
|
|
660
|
+
"--size-chart-document-id",
|
|
661
|
+
help=(
|
|
662
|
+
"size_chart_document_id: the quote's size chart document (optional). "
|
|
663
|
+
'Pass an empty string ("") to send it as null and leave the link without one.'
|
|
664
|
+
),
|
|
665
|
+
),
|
|
666
|
+
component_specifications_json: str | None = typer.Option(
|
|
667
|
+
None,
|
|
668
|
+
"--component-specifications-json",
|
|
669
|
+
help=(
|
|
670
|
+
"component_specifications: free-form specification rows (optional, JSON array). "
|
|
671
|
+
'Example: \'[{"component":"Fabric","specification":"100% cotton, 220gsm"}]\''
|
|
672
|
+
),
|
|
673
|
+
),
|
|
674
|
+
link_type_kind: str | None = typer.Option(
|
|
675
|
+
None,
|
|
676
|
+
"--type",
|
|
677
|
+
help=(
|
|
678
|
+
"type: typed-link kind (optional; default MANUFACTURE). "
|
|
679
|
+
"MANUFACTURE, PRODUCT_DEVELOP, or a material kind (FABRIC, BUTTON, ZIPPER, "
|
|
680
|
+
"ELASTIC, LABEL, HOOK, SNAP, CORD, BUCKLE, TAPE, PATCH, LACE, EYELET)."
|
|
681
|
+
),
|
|
682
|
+
),
|
|
683
|
+
attributes_json: str | None = typer.Option(
|
|
684
|
+
None,
|
|
685
|
+
"--attributes-json",
|
|
686
|
+
help="attributes: LinkAttributes-shaped SKU metadata (optional, JSON object).",
|
|
687
|
+
),
|
|
563
688
|
document_ids_json: str | None = typer.Option(
|
|
564
689
|
None,
|
|
565
690
|
"--document-ids-json",
|
|
@@ -594,76 +719,78 @@ def register(app: typer.Typer) -> None:
|
|
|
594
719
|
"""
|
|
595
720
|
Submit a new quote link to a quote request as admin.
|
|
596
721
|
"""
|
|
597
|
-
|
|
598
|
-
def _parse_json_option(raw: str, option_name: str) -> object:
|
|
599
|
-
try:
|
|
600
|
-
return json.loads(raw)
|
|
601
|
-
except json.JSONDecodeError as e:
|
|
602
|
-
console.print(f"[red]{option_name} must be valid JSON: {e}[/red]")
|
|
603
|
-
raise typer.Exit(1) from e
|
|
604
|
-
|
|
605
|
-
production_price_tiers = _parse_json_option(
|
|
606
|
-
production_price_tiers_json, "--production-price-tiers-json"
|
|
607
|
-
)
|
|
608
|
-
deliverables = _parse_json_option(deliverables_json, "--deliverables-json")
|
|
609
|
-
if not isinstance(production_price_tiers, list):
|
|
610
|
-
console.print("[red]--production-price-tiers-json must be a JSON array (list).[/red]")
|
|
611
|
-
raise typer.Exit(1)
|
|
612
|
-
if not isinstance(deliverables, list):
|
|
613
|
-
console.print("[red]--deliverables-json must be a JSON array (list).[/red]")
|
|
614
|
-
raise typer.Exit(1)
|
|
722
|
+
deliverables = _parse_json_list_option(deliverables_json, "--deliverables-json")
|
|
615
723
|
|
|
616
724
|
body: dict[str, object] = {
|
|
617
725
|
"link_name": link_name,
|
|
618
726
|
"currency": currency,
|
|
619
727
|
"sampling_price": {
|
|
620
|
-
|
|
728
|
+
# `value`, not `amount`: Price.to_domain reads `value` and nothing
|
|
729
|
+
# else, so an `amount` key used to land the quote at zero.
|
|
730
|
+
"value": sampling_price_value,
|
|
621
731
|
"currency": currency,
|
|
622
732
|
"valid_until": sampling_price_valid_until,
|
|
623
733
|
},
|
|
624
|
-
"production_price_tiers": production_price_tiers,
|
|
625
734
|
"sample_lead_time": sample_lead_time,
|
|
626
|
-
"lead_time": lead_time,
|
|
627
735
|
"deliverables": deliverables,
|
|
628
736
|
"customer_note": customer_note,
|
|
629
737
|
}
|
|
630
738
|
|
|
739
|
+
if production_price_tiers_json is not None:
|
|
740
|
+
body["production_price_tiers"] = _parse_json_list_option(
|
|
741
|
+
production_price_tiers_json, "--production-price-tiers-json"
|
|
742
|
+
)
|
|
743
|
+
if lead_time is not None:
|
|
744
|
+
body["lead_time"] = lead_time
|
|
745
|
+
if extra_sample_price_value is not None:
|
|
746
|
+
body["extra_sample_price"] = {
|
|
747
|
+
"value": extra_sample_price_value,
|
|
748
|
+
"currency": currency,
|
|
749
|
+
}
|
|
631
750
|
if link_id:
|
|
632
751
|
body["link_id"] = link_id
|
|
633
752
|
if link_type:
|
|
634
753
|
body["link_type"] = link_type
|
|
635
754
|
if moq is not None:
|
|
636
755
|
body["moq"] = moq
|
|
756
|
+
if moq_applies_to:
|
|
757
|
+
body["moq_applies_to"] = _validate_moq_applies_to(moq_applies_to)
|
|
637
758
|
if production_price_incoterm is not None:
|
|
638
759
|
body["production_price_incoterm"] = production_price_incoterm
|
|
639
760
|
if production_payment_term is not None:
|
|
640
761
|
body["production_payment_term"] = production_payment_term
|
|
762
|
+
if material_ship_to_json is not None:
|
|
763
|
+
body["material_ship_to_address"] = _parse_json_object_option(
|
|
764
|
+
material_ship_to_json, "--material-ship-to-json"
|
|
765
|
+
)
|
|
766
|
+
if available_sizes_json is not None:
|
|
767
|
+
body["available_sizes"] = _parse_json_list_option(
|
|
768
|
+
available_sizes_json, "--available-sizes-json"
|
|
769
|
+
)
|
|
770
|
+
if available_colors_json is not None:
|
|
771
|
+
body["available_colors"] = _parse_json_list_option(
|
|
772
|
+
available_colors_json, "--available-colors-json"
|
|
773
|
+
)
|
|
774
|
+
if size_chart_document_id is not None:
|
|
775
|
+
body["size_chart_document_id"] = size_chart_document_id or None
|
|
776
|
+
if component_specifications_json is not None:
|
|
777
|
+
body["component_specifications"] = _parse_json_list_option(
|
|
778
|
+
component_specifications_json, "--component-specifications-json"
|
|
779
|
+
)
|
|
780
|
+
if link_type_kind is not None:
|
|
781
|
+
body["type"] = link_type_kind
|
|
782
|
+
if attributes_json is not None:
|
|
783
|
+
body["attributes"] = _parse_json_object_option(attributes_json, "--attributes-json")
|
|
641
784
|
if document_ids_json is not None:
|
|
642
|
-
|
|
643
|
-
if not isinstance(parsed, list):
|
|
644
|
-
console.print("[red]--document-ids-json must be a JSON array (list).[/red]")
|
|
645
|
-
raise typer.Exit(1)
|
|
646
|
-
body["document_ids"] = parsed
|
|
785
|
+
body["document_ids"] = _parse_json_list_option(document_ids_json, "--document-ids-json")
|
|
647
786
|
if additional_deliverables_json is not None:
|
|
648
|
-
|
|
787
|
+
body["additional_deliverables"] = _parse_json_list_option(
|
|
649
788
|
additional_deliverables_json, "--additional-deliverables-json"
|
|
650
789
|
)
|
|
651
|
-
if not isinstance(parsed, list):
|
|
652
|
-
console.print(
|
|
653
|
-
"[red]--additional-deliverables-json must be a JSON array (list).[/red]"
|
|
654
|
-
)
|
|
655
|
-
raise typer.Exit(1)
|
|
656
|
-
body["additional_deliverables"] = parsed
|
|
657
790
|
if one_time_production_fees_json is not None:
|
|
658
|
-
|
|
791
|
+
body["one_time_production_fees"] = _parse_json_list_option(
|
|
659
792
|
one_time_production_fees_json, "--one-time-production-fees-json"
|
|
660
793
|
)
|
|
661
|
-
if not isinstance(parsed, list):
|
|
662
|
-
console.print(
|
|
663
|
-
"[red]--one-time-production-fees-json must be a JSON array (list).[/red]"
|
|
664
|
-
)
|
|
665
|
-
raise typer.Exit(1)
|
|
666
|
-
body["one_time_production_fees"] = parsed
|
|
667
794
|
if refundable_terms_json is not None:
|
|
668
795
|
body["refundable_terms"] = _parse_json_option(
|
|
669
796
|
refundable_terms_json, "--refundable-terms-json"
|
|
@@ -686,3 +813,283 @@ def register(app: typer.Typer) -> None:
|
|
|
686
813
|
body=body,
|
|
687
814
|
),
|
|
688
815
|
)
|
|
816
|
+
|
|
817
|
+
@app.command(
|
|
818
|
+
"update-supplier-quote",
|
|
819
|
+
help=(
|
|
820
|
+
"POST /admin/product/edit_supplier_link_override_info — admin updates an "
|
|
821
|
+
"existing quote (supplier link) in place. Only the options you pass are "
|
|
822
|
+
"sent, and the backend leaves every field you omit alone. "
|
|
823
|
+
"The override door does not read extra_sample_price, moq_applies_to, "
|
|
824
|
+
"material_ship_to_address or size_chart_document_id — those can only be "
|
|
825
|
+
"set at submit time."
|
|
826
|
+
),
|
|
827
|
+
)
|
|
828
|
+
def update_supplier_quote(
|
|
829
|
+
product_id: str = typer.Argument(
|
|
830
|
+
...,
|
|
831
|
+
help="product_id: the product the quote (supplier link) hangs off.",
|
|
832
|
+
),
|
|
833
|
+
link_id: str = typer.Argument(
|
|
834
|
+
...,
|
|
835
|
+
help="link_id: the supplier link to update.",
|
|
836
|
+
),
|
|
837
|
+
supplier_id: str | None = typer.Option(
|
|
838
|
+
None,
|
|
839
|
+
"--supplier-id",
|
|
840
|
+
help="supplier_id: reassign the link to another supplier (validated by the backend).",
|
|
841
|
+
),
|
|
842
|
+
link_name: str | None = typer.Option(
|
|
843
|
+
None,
|
|
844
|
+
"--link-name",
|
|
845
|
+
help="link_name: quote name.",
|
|
846
|
+
),
|
|
847
|
+
currency: str | None = typer.Option(
|
|
848
|
+
None,
|
|
849
|
+
"--currency",
|
|
850
|
+
help="currency: quote currency (also the fallback currency for fees).",
|
|
851
|
+
),
|
|
852
|
+
sampling_price_value: float | None = typer.Option(
|
|
853
|
+
None,
|
|
854
|
+
"--sampling-price-value",
|
|
855
|
+
help=(
|
|
856
|
+
"sampling_price.value: new sampling price. The whole sampling_price "
|
|
857
|
+
"object is replaced, so --sampling-price-valid-until is required with it."
|
|
858
|
+
),
|
|
859
|
+
),
|
|
860
|
+
sampling_price_valid_until: str | None = typer.Option(
|
|
861
|
+
None,
|
|
862
|
+
"--sampling-price-valid-until",
|
|
863
|
+
help="sampling_price.valid_until: expiry to store with the new sampling price.",
|
|
864
|
+
),
|
|
865
|
+
sampling_price_options_json: str | None = typer.Option(
|
|
866
|
+
None,
|
|
867
|
+
"--sampling-price-options-json",
|
|
868
|
+
help="sampling_price_options: multi-option sampling prices (JSON array).",
|
|
869
|
+
),
|
|
870
|
+
production_price_tiers_json: str | None = typer.Option(
|
|
871
|
+
None,
|
|
872
|
+
"--production-price-tiers-json",
|
|
873
|
+
help=(
|
|
874
|
+
"production_price_tiers: the link's single tier schedule (JSON array; "
|
|
875
|
+
"pass [] to clear it). "
|
|
876
|
+
'Example: \'[{"min_qty":100,"max_qty":499,"unit_price":12.5}]\''
|
|
877
|
+
),
|
|
878
|
+
),
|
|
879
|
+
pricing_groups_json: str | None = typer.Option(
|
|
880
|
+
None,
|
|
881
|
+
"--pricing-groups-json",
|
|
882
|
+
help=(
|
|
883
|
+
"pricing_groups: several tier schedules on one quote (JSON array; "
|
|
884
|
+
"pass [] to clear them). A group names the cart keys it prices "
|
|
885
|
+
'("{size},{color_hex}") and carries its own tiers, MOQ and incoterm; '
|
|
886
|
+
"tier_basis/moq_basis are VARIANT, GROUP (default) or ORDER, and "
|
|
887
|
+
"max_qty 0 means an open-ended top tier. "
|
|
888
|
+
'Example: \'[{"name":"Core","variants":["S,#000000"],'
|
|
889
|
+
'"price_tiers":[{"min_qty":100,"max_qty":0,"unit_price":12.5}],'
|
|
890
|
+
'"tier_basis":"VARIANT"}]\''
|
|
891
|
+
),
|
|
892
|
+
),
|
|
893
|
+
moq: int | None = typer.Option(
|
|
894
|
+
None,
|
|
895
|
+
"--moq",
|
|
896
|
+
help="moq: minimum order quantity.",
|
|
897
|
+
),
|
|
898
|
+
sample_lead_time: str | None = typer.Option(
|
|
899
|
+
None,
|
|
900
|
+
"--sample-lead-time",
|
|
901
|
+
help="sample_lead_time: sampling lead time.",
|
|
902
|
+
),
|
|
903
|
+
lead_time: str | None = typer.Option(
|
|
904
|
+
None,
|
|
905
|
+
"--lead-time",
|
|
906
|
+
help="lead_time: production lead time.",
|
|
907
|
+
),
|
|
908
|
+
production_price_incoterm: str | None = typer.Option(
|
|
909
|
+
None,
|
|
910
|
+
"--production-price-incoterm",
|
|
911
|
+
help=(
|
|
912
|
+
"production_price_incoterm: EXW or FOB; pass `null` to clear it. "
|
|
913
|
+
"An unrecognised value is rejected by the backend."
|
|
914
|
+
),
|
|
915
|
+
),
|
|
916
|
+
production_payment_term: str | None = typer.Option(
|
|
917
|
+
None,
|
|
918
|
+
"--production-payment-term",
|
|
919
|
+
help='production_payment_term: deposit ratio, e.g. "40". Pass `null` to clear it.',
|
|
920
|
+
),
|
|
921
|
+
customer_note: str | None = typer.Option(
|
|
922
|
+
None,
|
|
923
|
+
"--customer-note",
|
|
924
|
+
help="customer_note: note for customer/platform (empty string allowed).",
|
|
925
|
+
),
|
|
926
|
+
deliverables_json: str | None = typer.Option(
|
|
927
|
+
None,
|
|
928
|
+
"--deliverables-json",
|
|
929
|
+
help="deliverables: deliverables array (JSON), e.g. '[\"size_set\"]'.",
|
|
930
|
+
),
|
|
931
|
+
additional_deliverables_json: str | None = typer.Option(
|
|
932
|
+
None,
|
|
933
|
+
"--additional-deliverables-json",
|
|
934
|
+
help="additional_deliverables: extra deliverables array (JSON).",
|
|
935
|
+
),
|
|
936
|
+
one_time_production_fees_json: str | None = typer.Option(
|
|
937
|
+
None,
|
|
938
|
+
"--one-time-production-fees-json",
|
|
939
|
+
help="one_time_production_fees: one-time production fees array (JSON).",
|
|
940
|
+
),
|
|
941
|
+
refundable_terms_json: str | None = typer.Option(
|
|
942
|
+
None,
|
|
943
|
+
"--refundable-terms-json",
|
|
944
|
+
help="refundable_terms: refundable terms (JSON object; pass `null` to clear).",
|
|
945
|
+
),
|
|
946
|
+
available_sizes_json: str | None = typer.Option(
|
|
947
|
+
None,
|
|
948
|
+
"--available-sizes-json",
|
|
949
|
+
help=(
|
|
950
|
+
"available_sizes: sizes the quote is priced against (JSON array of strings). "
|
|
951
|
+
"Pricing groups claim cart keys built from these."
|
|
952
|
+
),
|
|
953
|
+
),
|
|
954
|
+
available_colors_json: str | None = typer.Option(
|
|
955
|
+
None,
|
|
956
|
+
"--available-colors-json",
|
|
957
|
+
help=(
|
|
958
|
+
"available_colors: colours the quote is priced against (JSON array of "
|
|
959
|
+
"{color_name, hex_code, pantone_code?})."
|
|
960
|
+
),
|
|
961
|
+
),
|
|
962
|
+
component_specifications_json: str | None = typer.Option(
|
|
963
|
+
None,
|
|
964
|
+
"--component-specifications-json",
|
|
965
|
+
help="component_specifications: free-form specification rows (JSON array).",
|
|
966
|
+
),
|
|
967
|
+
link_type_kind: str | None = typer.Option(
|
|
968
|
+
None,
|
|
969
|
+
"--type",
|
|
970
|
+
help="type: typed-link kind (MANUFACTURE, PRODUCT_DEVELOP, FABRIC, BUTTON, ...).",
|
|
971
|
+
),
|
|
972
|
+
attributes_json: str | None = typer.Option(
|
|
973
|
+
None,
|
|
974
|
+
"--attributes-json",
|
|
975
|
+
help="attributes: LinkAttributes-shaped SKU metadata (JSON object).",
|
|
976
|
+
),
|
|
977
|
+
document_ids_json: str | None = typer.Option(
|
|
978
|
+
None,
|
|
979
|
+
"--document-ids-json",
|
|
980
|
+
help="document_ids: related document ID array (JSON), e.g. '[\"doc_1\"]'.",
|
|
981
|
+
),
|
|
982
|
+
) -> None:
|
|
983
|
+
"""
|
|
984
|
+
Update an existing quote (supplier link) as admin.
|
|
985
|
+
"""
|
|
986
|
+
override: dict[str, object] = {}
|
|
987
|
+
|
|
988
|
+
if (sampling_price_value is None) != (sampling_price_valid_until is None):
|
|
989
|
+
console.print(
|
|
990
|
+
"[red]--sampling-price-value and --sampling-price-valid-until must be "
|
|
991
|
+
"passed together: the backend replaces the whole sampling_price object, "
|
|
992
|
+
"so sending one without the other drops the other.[/red]"
|
|
993
|
+
)
|
|
994
|
+
raise typer.Exit(1)
|
|
995
|
+
|
|
996
|
+
if supplier_id is not None:
|
|
997
|
+
override["supplier_id"] = supplier_id
|
|
998
|
+
if link_name is not None:
|
|
999
|
+
override["link_name"] = link_name
|
|
1000
|
+
if currency is not None:
|
|
1001
|
+
override["currency"] = currency
|
|
1002
|
+
if sampling_price_value is not None:
|
|
1003
|
+
override["sampling_price"] = {
|
|
1004
|
+
"value": sampling_price_value,
|
|
1005
|
+
"valid_until": sampling_price_valid_until,
|
|
1006
|
+
**({"currency": currency} if currency else {}),
|
|
1007
|
+
}
|
|
1008
|
+
if sampling_price_options_json is not None:
|
|
1009
|
+
override["sampling_price_options"] = _parse_json_list_option(
|
|
1010
|
+
sampling_price_options_json, "--sampling-price-options-json"
|
|
1011
|
+
)
|
|
1012
|
+
if production_price_tiers_json is not None:
|
|
1013
|
+
override["production_price_tiers"] = _parse_json_list_option(
|
|
1014
|
+
production_price_tiers_json, "--production-price-tiers-json"
|
|
1015
|
+
)
|
|
1016
|
+
if pricing_groups_json is not None:
|
|
1017
|
+
override["pricing_groups"] = _parse_json_list_option(
|
|
1018
|
+
pricing_groups_json, "--pricing-groups-json"
|
|
1019
|
+
)
|
|
1020
|
+
if moq is not None:
|
|
1021
|
+
override["moq"] = moq
|
|
1022
|
+
if sample_lead_time is not None:
|
|
1023
|
+
override["sample_lead_time"] = sample_lead_time
|
|
1024
|
+
if lead_time is not None:
|
|
1025
|
+
override["lead_time"] = lead_time
|
|
1026
|
+
if production_price_incoterm is not None:
|
|
1027
|
+
override["production_price_incoterm"] = _none_if_null_literal(production_price_incoterm)
|
|
1028
|
+
if production_payment_term is not None:
|
|
1029
|
+
override["production_payment_term"] = _none_if_null_literal(production_payment_term)
|
|
1030
|
+
if customer_note is not None:
|
|
1031
|
+
override["customer_note"] = customer_note
|
|
1032
|
+
if deliverables_json is not None:
|
|
1033
|
+
override["deliverables"] = _parse_json_list_option(
|
|
1034
|
+
deliverables_json, "--deliverables-json"
|
|
1035
|
+
)
|
|
1036
|
+
if additional_deliverables_json is not None:
|
|
1037
|
+
override["additional_deliverables"] = _parse_json_list_option(
|
|
1038
|
+
additional_deliverables_json, "--additional-deliverables-json"
|
|
1039
|
+
)
|
|
1040
|
+
if one_time_production_fees_json is not None:
|
|
1041
|
+
override["one_time_production_fees"] = _parse_json_list_option(
|
|
1042
|
+
one_time_production_fees_json, "--one-time-production-fees-json"
|
|
1043
|
+
)
|
|
1044
|
+
if refundable_terms_json is not None:
|
|
1045
|
+
override["refundable_terms"] = _parse_json_option(
|
|
1046
|
+
refundable_terms_json, "--refundable-terms-json"
|
|
1047
|
+
)
|
|
1048
|
+
if available_sizes_json is not None:
|
|
1049
|
+
override["available_sizes"] = _parse_json_list_option(
|
|
1050
|
+
available_sizes_json, "--available-sizes-json"
|
|
1051
|
+
)
|
|
1052
|
+
if available_colors_json is not None:
|
|
1053
|
+
override["available_colors"] = _parse_json_list_option(
|
|
1054
|
+
available_colors_json, "--available-colors-json"
|
|
1055
|
+
)
|
|
1056
|
+
if component_specifications_json is not None:
|
|
1057
|
+
override["component_specifications"] = _parse_json_list_option(
|
|
1058
|
+
component_specifications_json, "--component-specifications-json"
|
|
1059
|
+
)
|
|
1060
|
+
if link_type_kind is not None:
|
|
1061
|
+
override["type"] = link_type_kind
|
|
1062
|
+
if attributes_json is not None:
|
|
1063
|
+
override["attributes"] = _parse_json_object_option(attributes_json, "--attributes-json")
|
|
1064
|
+
if document_ids_json is not None:
|
|
1065
|
+
override["document_ids"] = _parse_json_list_option(
|
|
1066
|
+
document_ids_json, "--document-ids-json"
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
if not override:
|
|
1070
|
+
console.print(
|
|
1071
|
+
"[red]Nothing to update: pass at least one field option "
|
|
1072
|
+
"(the backend rejects an empty override_info).[/red]"
|
|
1073
|
+
)
|
|
1074
|
+
raise typer.Exit(1)
|
|
1075
|
+
|
|
1076
|
+
# Pricing groups claim cart keys built from the link's own variant lists.
|
|
1077
|
+
# Groups without them price variants no order can contain, so say so
|
|
1078
|
+
# rather than let the quote look fine and misprice later.
|
|
1079
|
+
if override.get("pricing_groups") and not (
|
|
1080
|
+
"available_sizes" in override or "available_colors" in override
|
|
1081
|
+
):
|
|
1082
|
+
console.print(
|
|
1083
|
+
"[yellow]Warning: --pricing-groups-json without --available-sizes-json / "
|
|
1084
|
+
"--available-colors-json relies on the variant lists already stored on the "
|
|
1085
|
+
"link. Check them with `get-supplier-links-by-supplier-id`.[/yellow]"
|
|
1086
|
+
)
|
|
1087
|
+
|
|
1088
|
+
print_json_or_exit(
|
|
1089
|
+
console,
|
|
1090
|
+
lambda: api_client.edit_supplier_link_override_info(
|
|
1091
|
+
product_id=product_id,
|
|
1092
|
+
link_id=link_id,
|
|
1093
|
+
override_info=override,
|
|
1094
|
+
),
|
|
1095
|
+
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.21"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|