makea-cli 0.2.9__tar.gz → 0.2.10__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.
Files changed (67) hide show
  1. {makea_cli-0.2.9 → makea_cli-0.2.10}/CHANGELOG.md +14 -0
  2. {makea_cli-0.2.9 → makea_cli-0.2.10}/PKG-INFO +1 -1
  3. makea_cli-0.2.10/makea_cli/__init__.py +1 -0
  4. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/api_client.py +93 -0
  5. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/brand/cmd.py +89 -0
  6. {makea_cli-0.2.9 → makea_cli-0.2.10}/pyproject.toml +1 -1
  7. {makea_cli-0.2.9 → makea_cli-0.2.10}/tests/golden_manifest.json +171 -0
  8. makea_cli-0.2.10/tests/test_pm_reads.py +69 -0
  9. makea_cli-0.2.9/makea_cli/__init__.py +0 -1
  10. {makea_cli-0.2.9 → makea_cli-0.2.10}/.claude/skills/create-supplier-profile/SKILL.md +0 -0
  11. {makea_cli-0.2.9 → makea_cli-0.2.10}/.claude/skills/lark-cli-setup/SKILL.md +0 -0
  12. {makea_cli-0.2.9 → makea_cli-0.2.10}/.claude/skills/link-supplier-user/SKILL.md +0 -0
  13. {makea_cli-0.2.9 → makea_cli-0.2.10}/.claude/skills/makea-cli-setup/SKILL.md +0 -0
  14. {makea_cli-0.2.9 → makea_cli-0.2.10}/.claude/skills/payment-revenue-report/SKILL.md +0 -0
  15. {makea_cli-0.2.9 → makea_cli-0.2.10}/.claude/skills/supplier-user-id-migration/SKILL.md +0 -0
  16. {makea_cli-0.2.9 → makea_cli-0.2.10}/.gitignore +0 -0
  17. {makea_cli-0.2.9 → makea_cli-0.2.10}/README.md +0 -0
  18. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea-collection-upload/SKILL.md +0 -0
  19. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea-collection-upload/scripts/create_products.py +0 -0
  20. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/__main__.py +0 -0
  21. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/auth_pkce.py +0 -0
  22. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/broker.py +0 -0
  23. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/__init__.py +0 -0
  24. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/_util.py +0 -0
  25. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/analytics/__init__.py +0 -0
  26. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/analytics/cmd.py +0 -0
  27. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/auth/__init__.py +0 -0
  28. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/auth/cmd.py +0 -0
  29. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/brand/__init__.py +0 -0
  30. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/designer/__init__.py +0 -0
  31. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/designer/cmd.py +0 -0
  32. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/directory/__init__.py +0 -0
  33. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/directory/cmd.py +0 -0
  34. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/document/__init__.py +0 -0
  35. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/document/cmd.py +0 -0
  36. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/email/__init__.py +0 -0
  37. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/email/cmd.py +0 -0
  38. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/factory/__init__.py +0 -0
  39. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/factory/cmd.py +0 -0
  40. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/library/__init__.py +0 -0
  41. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/library/cmd.py +0 -0
  42. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/metrics/__init__.py +0 -0
  43. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/metrics/cmd.py +0 -0
  44. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/orders/__init__.py +0 -0
  45. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/orders/cmd.py +0 -0
  46. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/product/__init__.py +0 -0
  47. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/product/cmd.py +0 -0
  48. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/production/__init__.py +0 -0
  49. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/production/cmd.py +0 -0
  50. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/production/pricing_spec.py +0 -0
  51. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/supplier/__init__.py +0 -0
  52. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/commands/supplier/cmd.py +0 -0
  53. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/config.py +0 -0
  54. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/errors.py +0 -0
  55. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/identity.py +0 -0
  56. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/main.py +0 -0
  57. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/output.py +0 -0
  58. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/posthog_client.py +0 -0
  59. {makea_cli-0.2.9 → makea_cli-0.2.10}/makea_cli/registry.py +0 -0
  60. {makea_cli-0.2.9 → makea_cli-0.2.10}/npm/makea-cli/README.md +0 -0
  61. {makea_cli-0.2.9 → makea_cli-0.2.10}/npm/makea-cli/makea-cli.mjs +0 -0
  62. {makea_cli-0.2.9 → makea_cli-0.2.10}/npm/makea-cli/package.json +0 -0
  63. {makea_cli-0.2.9 → makea_cli-0.2.10}/tests/test_broker.py +0 -0
  64. {makea_cli-0.2.9 → makea_cli-0.2.10}/tests/test_output_contract.py +0 -0
  65. {makea_cli-0.2.9 → makea_cli-0.2.10}/tests/test_persona_commands.py +0 -0
  66. {makea_cli-0.2.9 → makea_cli-0.2.10}/tests/test_registry.py +0 -0
  67. {makea_cli-0.2.9 → makea_cli-0.2.10}/uv.lock +0 -0
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.10
4
+
5
+ - PM reads for a brand (and Beeboo working for one):
6
+ - `designer get tna`: the Time & Action calendar, every pack's arrival with
7
+ how sure it is (done / confirmed / estimated) and its launch day.
8
+ - `designer list milestone --product <id>`: the bulk order's production
9
+ milestones, flattened (step, milestone, status, submitted/approved).
10
+ - `designer list note --product <id> --channel <key> [--since <date>]`: a
11
+ channel's messages without a search keyword, optionally only recent ones.
12
+ - `designer list fit-round --product <id>`: fit rounds and the corrections
13
+ carried into bulk.
14
+ - `designer get costing --style <id>`: one unit of each pack, factory price,
15
+ fees, the brand's freight/duty estimates, target, RRP, margin.
16
+
3
17
  ## 0.2.9
4
18
 
5
19
  - The analytics commands (`admin get analytics-query`, `active-users`,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: makea-cli
3
- Version: 0.2.9
3
+ Version: 0.2.10
4
4
  Summary: Makea admin CLI (browser login + Makea admin HTTP API)
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: click<9,>=8.1
@@ -0,0 +1 @@
1
+ __version__ = "0.2.10"
@@ -1359,6 +1359,99 @@ def supplier_list_quote_edit_requests(quote_request_id: str) -> Any:
1359
1359
  return get_json(f"/supplier/quote_requests/{quote_request_id}/quote_edit_requests")
1360
1360
 
1361
1361
 
1362
+ def designer_get_tna() -> Any:
1363
+ """GET /designer/tna_calendar — every pack's five steps and arrival, with
1364
+ the launch it is measured against when the arrival is confirmed."""
1365
+ return get_json("/designer/tna_calendar")
1366
+
1367
+
1368
+ def designer_list_milestones(product_id: str) -> Any:
1369
+ """Production milestones on a pack's bulk orders, compact: what is done,
1370
+ submitted or waiting, never a lateness the factory did not commit to."""
1371
+ raw = get_json(
1372
+ f"/designer/products/{product_id}/production_order_details",
1373
+ params={"type": "PRODUCTION"},
1374
+ )
1375
+ orders = (
1376
+ raw.get("production_orders_details") or raw.get("orders")
1377
+ if isinstance(raw, dict)
1378
+ else raw
1379
+ )
1380
+ out = []
1381
+ for order in orders or []:
1382
+ if not isinstance(order, dict):
1383
+ continue
1384
+ for step in order.get("production_progress") or []:
1385
+ meta = step.get("metadata") or {}
1386
+ for m in meta.get("milestones") or []:
1387
+ if not isinstance(m, dict):
1388
+ continue
1389
+ out.append(
1390
+ {
1391
+ "order_id": order.get("production_order_id"),
1392
+ "step": step.get("progress_type"),
1393
+ "milestone": m.get("title"),
1394
+ "status": m.get("status"),
1395
+ "days_from_payment": m.get("time_window"),
1396
+ "submitted_at": m.get("submitted_at"),
1397
+ "approval_result": m.get("approval_result"),
1398
+ "completed_at": m.get("completed_at"),
1399
+ }
1400
+ )
1401
+ return {"product_id": product_id, "milestones": out}
1402
+
1403
+
1404
+ def designer_list_notes(
1405
+ product_id: str, channel_key: str, *, since: str | None = None
1406
+ ) -> Any:
1407
+ """A channel's messages, oldest first, optionally only those after
1408
+ ``since`` (ISO date or datetime): what the factory said this week."""
1409
+ raw = get_json(f"/designer/products/{product_id}/channels/{channel_key}/notes")
1410
+ notes = raw.get("notes") if isinstance(raw, dict) else raw
1411
+ out = []
1412
+ for n in notes or []:
1413
+ if not isinstance(n, dict):
1414
+ continue
1415
+ if since and str(n.get("created_at") or "") < since:
1416
+ continue
1417
+ out.append(
1418
+ {
1419
+ "note_id": n.get("note_id"),
1420
+ "at": n.get("created_at"),
1421
+ "from": n.get("created_by_type"),
1422
+ "supplier": n.get("supplier_name"),
1423
+ "text": n.get("content"),
1424
+ "replies": len(n.get("replies") or []),
1425
+ }
1426
+ )
1427
+ return {"product_id": product_id, "channel": channel_key, "notes": out}
1428
+
1429
+
1430
+ def designer_list_fit_rounds(product_id: str) -> Any:
1431
+ """GET /designer/products/{id}/fit_rounds — every fit round on the pack and
1432
+ the corrections carried into bulk."""
1433
+ return get_json(f"/designer/products/{product_id}/fit_rounds")
1434
+
1435
+
1436
+ def designer_get_costing(style_id: str) -> Any:
1437
+ """One unit of each pack on a product card: factory price, fees, the
1438
+ brand's freight and duty estimates, target, RRP, margin."""
1439
+ raw = get_json(f"/designer/styles/{style_id}/overview")
1440
+ packs = raw.get("packs") if isinstance(raw, dict) else []
1441
+ return {
1442
+ "style_id": style_id,
1443
+ "packs": [
1444
+ {
1445
+ "product_id": p.get("product_id"),
1446
+ "pack": p.get("pack_label"),
1447
+ "costing": p.get("costing"),
1448
+ }
1449
+ for p in packs or []
1450
+ if isinstance(p, dict)
1451
+ ],
1452
+ }
1453
+
1454
+
1362
1455
  def designer_list_actions(*, product_id: str | None = None) -> Any:
1363
1456
  """GET /designer/actions — the brand's pending actions, optionally for one product."""
1364
1457
  params = {"product_id": product_id} if product_id else None
@@ -212,6 +212,95 @@ def register(app: typer.Typer) -> None:
212
212
  """List a product's channels."""
213
213
  print_json_or_exit(console, lambda: api_client.designer_list_channels(product))
214
214
 
215
+ @command(
216
+ app,
217
+ "designer",
218
+ "get",
219
+ "tna",
220
+ api="GET /designer/tna_calendar",
221
+ help=(
222
+ "GET /designer/tna_calendar — when each pack arrives and how sure that is "
223
+ "(done / confirmed / estimated), with its launch day."
224
+ ),
225
+ )
226
+ def get_tna() -> None:
227
+ """The Time & Action calendar."""
228
+ print_json_or_exit(console, lambda: api_client.designer_get_tna())
229
+
230
+ @command(
231
+ app,
232
+ "designer",
233
+ "list",
234
+ "milestone",
235
+ api="GET /designer/products/{product_id}/production_order_details",
236
+ help=(
237
+ "GET /designer/products/{id}/production_order_details — the bulk order's "
238
+ "production milestones: done, submitted, waiting."
239
+ ),
240
+ )
241
+ def list_milestones(
242
+ product: str = typer.Option(..., "--product", "-p", help="Product id."),
243
+ ) -> None:
244
+ """List a pack's production milestones."""
245
+ print_json_or_exit(console, lambda: api_client.designer_list_milestones(product))
246
+
247
+ @command(
248
+ app,
249
+ "designer",
250
+ "list",
251
+ "note",
252
+ api="GET /designer/products/{product_id}/channels/{channel_key}/notes",
253
+ help=(
254
+ "GET /designer/products/{id}/channels/{key}/notes — a channel's messages, "
255
+ "oldest first; --since keeps the recent ones (no keyword needed)."
256
+ ),
257
+ )
258
+ def list_notes(
259
+ product: str = typer.Option(..., "--product", "-p", help="Product id."),
260
+ channel: str = typer.Option(..., "--channel", "-c", help="Channel key."),
261
+ since: str | None = typer.Option(
262
+ None, "--since", help="ISO date or datetime, e.g. 2026-10-01."
263
+ ),
264
+ ) -> None:
265
+ """List a channel's messages."""
266
+ print_json_or_exit(
267
+ console, lambda: api_client.designer_list_notes(product, channel, since=since)
268
+ )
269
+
270
+ @command(
271
+ app,
272
+ "designer",
273
+ "list",
274
+ "fit-round",
275
+ api="GET /designer/products/{product_id}/fit_rounds",
276
+ help=(
277
+ "GET /designer/products/{id}/fit_rounds — the pack's fit rounds and the "
278
+ "corrections carried into bulk."
279
+ ),
280
+ )
281
+ def list_fit_rounds(
282
+ product: str = typer.Option(..., "--product", "-p", help="Product id."),
283
+ ) -> None:
284
+ """List a pack's fit rounds."""
285
+ print_json_or_exit(console, lambda: api_client.designer_list_fit_rounds(product))
286
+
287
+ @command(
288
+ app,
289
+ "designer",
290
+ "get",
291
+ "costing",
292
+ api="GET /designer/styles/{style_id}/overview",
293
+ help=(
294
+ "GET /designer/styles/{id}/overview — what one unit of each pack costs and "
295
+ "the margin at RRP (freight and duty are the brand's estimates)."
296
+ ),
297
+ )
298
+ def get_costing(
299
+ style: str = typer.Option(..., "--style", "-s", help="Product card (style) id."),
300
+ ) -> None:
301
+ """A product card's unit cost and margin."""
302
+ print_json_or_exit(console, lambda: api_client.designer_get_costing(style))
303
+
215
304
  @command(
216
305
  app,
217
306
  "designer",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "makea-cli"
7
- version = "0.2.9"
7
+ version = "0.2.10"
8
8
  description = "Makea admin CLI (browser login + Makea admin HTTP API)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -5257,6 +5257,38 @@
5257
5257
  }
5258
5258
  ]
5259
5259
  },
5260
+ {
5261
+ "name": "designer get costing",
5262
+ "persona": "designer",
5263
+ "verb": "get",
5264
+ "noun": "costing",
5265
+ "access": "read",
5266
+ "api": "GET /designer/styles/{style_id}/overview",
5267
+ "external": null,
5268
+ "side_effects": [],
5269
+ "approval": "auto",
5270
+ "reversible": null,
5271
+ "idempotent": null,
5272
+ "limit": null,
5273
+ "legacy": [],
5274
+ "help": "GET /designer/styles/{id}/overview — what one unit of each pack costs and the margin at RRP (freight and duty are the brand's estimates).",
5275
+ "function": "makea_cli.commands.brand.cmd.register.<locals>.get_costing",
5276
+ "flags": [
5277
+ {
5278
+ "name": "style",
5279
+ "kind": "option",
5280
+ "opts": [
5281
+ "--style",
5282
+ "-s"
5283
+ ],
5284
+ "type": "text",
5285
+ "required": true,
5286
+ "multiple": false,
5287
+ "default": null,
5288
+ "help": "Product card (style) id."
5289
+ }
5290
+ ]
5291
+ },
5260
5292
  {
5261
5293
  "name": "designer get lifecycle",
5262
5294
  "persona": "designer",
@@ -5468,6 +5500,24 @@
5468
5500
  }
5469
5501
  ]
5470
5502
  },
5503
+ {
5504
+ "name": "designer get tna",
5505
+ "persona": "designer",
5506
+ "verb": "get",
5507
+ "noun": "tna",
5508
+ "access": "read",
5509
+ "api": "GET /designer/tna_calendar",
5510
+ "external": null,
5511
+ "side_effects": [],
5512
+ "approval": "auto",
5513
+ "reversible": null,
5514
+ "idempotent": null,
5515
+ "limit": null,
5516
+ "legacy": [],
5517
+ "help": "GET /designer/tna_calendar — when each pack arrives and how sure that is (done / confirmed / estimated), with its launch day.",
5518
+ "function": "makea_cli.commands.brand.cmd.register.<locals>.get_tna",
5519
+ "flags": []
5520
+ },
5471
5521
  {
5472
5522
  "name": "designer list action",
5473
5523
  "persona": "designer",
@@ -5550,6 +5600,38 @@
5550
5600
  "function": "makea_cli.commands.brand.cmd.register.<locals>.list_collections",
5551
5601
  "flags": []
5552
5602
  },
5603
+ {
5604
+ "name": "designer list fit-round",
5605
+ "persona": "designer",
5606
+ "verb": "list",
5607
+ "noun": "fit-round",
5608
+ "access": "read",
5609
+ "api": "GET /designer/products/{product_id}/fit_rounds",
5610
+ "external": null,
5611
+ "side_effects": [],
5612
+ "approval": "auto",
5613
+ "reversible": null,
5614
+ "idempotent": null,
5615
+ "limit": "client",
5616
+ "legacy": [],
5617
+ "help": "GET /designer/products/{id}/fit_rounds — the pack's fit rounds and the corrections carried into bulk.",
5618
+ "function": "makea_cli.commands.brand.cmd.register.<locals>.list_fit_rounds",
5619
+ "flags": [
5620
+ {
5621
+ "name": "product",
5622
+ "kind": "option",
5623
+ "opts": [
5624
+ "--product",
5625
+ "-p"
5626
+ ],
5627
+ "type": "text",
5628
+ "required": true,
5629
+ "multiple": false,
5630
+ "default": null,
5631
+ "help": "Product id."
5632
+ }
5633
+ ]
5634
+ },
5553
5635
  {
5554
5636
  "name": "designer list library-element",
5555
5637
  "persona": "designer",
@@ -5593,6 +5675,95 @@
5593
5675
  }
5594
5676
  ]
5595
5677
  },
5678
+ {
5679
+ "name": "designer list milestone",
5680
+ "persona": "designer",
5681
+ "verb": "list",
5682
+ "noun": "milestone",
5683
+ "access": "read",
5684
+ "api": "GET /designer/products/{product_id}/production_order_details",
5685
+ "external": null,
5686
+ "side_effects": [],
5687
+ "approval": "auto",
5688
+ "reversible": null,
5689
+ "idempotent": null,
5690
+ "limit": "client",
5691
+ "legacy": [],
5692
+ "help": "GET /designer/products/{id}/production_order_details — the bulk order's production milestones: done, submitted, waiting.",
5693
+ "function": "makea_cli.commands.brand.cmd.register.<locals>.list_milestones",
5694
+ "flags": [
5695
+ {
5696
+ "name": "product",
5697
+ "kind": "option",
5698
+ "opts": [
5699
+ "--product",
5700
+ "-p"
5701
+ ],
5702
+ "type": "text",
5703
+ "required": true,
5704
+ "multiple": false,
5705
+ "default": null,
5706
+ "help": "Product id."
5707
+ }
5708
+ ]
5709
+ },
5710
+ {
5711
+ "name": "designer list note",
5712
+ "persona": "designer",
5713
+ "verb": "list",
5714
+ "noun": "note",
5715
+ "access": "read",
5716
+ "api": "GET /designer/products/{product_id}/channels/{channel_key}/notes",
5717
+ "external": null,
5718
+ "side_effects": [],
5719
+ "approval": "auto",
5720
+ "reversible": null,
5721
+ "idempotent": null,
5722
+ "limit": "client",
5723
+ "legacy": [],
5724
+ "help": "GET /designer/products/{id}/channels/{key}/notes — a channel's messages, oldest first; --since keeps the recent ones (no keyword needed).",
5725
+ "function": "makea_cli.commands.brand.cmd.register.<locals>.list_notes",
5726
+ "flags": [
5727
+ {
5728
+ "name": "product",
5729
+ "kind": "option",
5730
+ "opts": [
5731
+ "--product",
5732
+ "-p"
5733
+ ],
5734
+ "type": "text",
5735
+ "required": true,
5736
+ "multiple": false,
5737
+ "default": null,
5738
+ "help": "Product id."
5739
+ },
5740
+ {
5741
+ "name": "channel",
5742
+ "kind": "option",
5743
+ "opts": [
5744
+ "--channel",
5745
+ "-c"
5746
+ ],
5747
+ "type": "text",
5748
+ "required": true,
5749
+ "multiple": false,
5750
+ "default": null,
5751
+ "help": "Channel key."
5752
+ },
5753
+ {
5754
+ "name": "since",
5755
+ "kind": "option",
5756
+ "opts": [
5757
+ "--since"
5758
+ ],
5759
+ "type": "text",
5760
+ "required": false,
5761
+ "multiple": false,
5762
+ "default": null,
5763
+ "help": "ISO date or datetime, e.g. 2026-10-01."
5764
+ }
5765
+ ]
5766
+ },
5596
5767
  {
5597
5768
  "name": "designer list payment",
5598
5769
  "persona": "designer",
@@ -0,0 +1,69 @@
1
+ """The PM reads Beeboo uses: milestones, recent notes, costing — trimmed to
2
+ what answers "will it be on time", "what did the factory say this week",
3
+ "what does it cost".
4
+
5
+ Run with ``.venv/bin/python -m unittest tests.test_pm_reads``.
6
+ """
7
+
8
+ import unittest
9
+ from unittest import mock
10
+
11
+ from makea_cli import api_client
12
+
13
+
14
+ class PmReadsTest(unittest.TestCase):
15
+ def test_milestones_are_flattened(self):
16
+ raw = {
17
+ "production_orders_details": [
18
+ {
19
+ "production_order_id": "po1",
20
+ "production_progress": [
21
+ {
22
+ "progress_type": "During Production",
23
+ "metadata": {
24
+ "milestones": [
25
+ {"title": "Cutting", "status": "completed", "time_window": ["3", "5"]},
26
+ ]
27
+ },
28
+ }
29
+ ],
30
+ }
31
+ ]
32
+ }
33
+ with mock.patch.object(api_client, "get_json", return_value=raw):
34
+ out = api_client.designer_list_milestones("p1")
35
+ self.assertEqual(
36
+ out["milestones"][0],
37
+ {
38
+ "order_id": "po1",
39
+ "step": "During Production",
40
+ "milestone": "Cutting",
41
+ "status": "completed",
42
+ "days_from_payment": ["3", "5"],
43
+ "submitted_at": None,
44
+ "approval_result": None,
45
+ "completed_at": None,
46
+ },
47
+ )
48
+
49
+ def test_notes_since_keep_the_recent_ones(self):
50
+ raw = {
51
+ "notes": [
52
+ {"note_id": "a", "created_at": "2026-09-30T10:00:00", "content": "old"},
53
+ {"note_id": "b", "created_at": "2026-10-05T10:00:00", "content": "new", "replies": [{}]},
54
+ ]
55
+ }
56
+ with mock.patch.object(api_client, "get_json", return_value=raw):
57
+ out = api_client.designer_list_notes("p1", "supplier:s", since="2026-10-01")
58
+ self.assertEqual([n["note_id"] for n in out["notes"]], ["b"])
59
+ self.assertEqual(out["notes"][0]["replies"], 1)
60
+
61
+ def test_costing_is_per_pack(self):
62
+ raw = {"packs": [{"product_id": "p1", "pack_label": "v1", "costing": {"make": 50}}]}
63
+ with mock.patch.object(api_client, "get_json", return_value=raw):
64
+ out = api_client.designer_get_costing("st1")
65
+ self.assertEqual(out["packs"], [{"product_id": "p1", "pack": "v1", "costing": {"make": 50}}])
66
+
67
+
68
+ if __name__ == "__main__":
69
+ unittest.main()
@@ -1 +0,0 @@
1
- __version__ = "0.2.9"
File without changes
File without changes
File without changes
File without changes