lambda-erp 0.8.16__tar.gz → 0.8.18__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 (89) hide show
  1. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/PKG-INFO +1 -9
  2. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/README.md +0 -8
  3. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/masters.py +139 -49
  4. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/frontend/src/api/client.ts +3 -3
  5. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/pyproject.toml +1 -1
  6. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/.gitignore +0 -0
  7. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/LICENSE +0 -0
  8. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/__init__.py +0 -0
  9. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/attachments.py +0 -0
  10. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/auth.py +0 -0
  11. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/bootstrap.py +0 -0
  12. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/chat.py +0 -0
  13. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/demo_limits.py +0 -0
  14. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/deps.py +0 -0
  15. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/errors.py +0 -0
  16. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/main.py +0 -0
  17. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/oauth.py +0 -0
  18. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/pdf.py +0 -0
  19. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/providers.py +0 -0
  20. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/remarks_md.py +0 -0
  21. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/__init__.py +0 -0
  22. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/accounting.py +0 -0
  23. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/actions.py +0 -0
  24. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/admin.py +0 -0
  25. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/analytics.py +0 -0
  26. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/availability.py +0 -0
  27. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/bank_reconciliation.py +0 -0
  28. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/chat_api.py +0 -0
  29. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/documents.py +0 -0
  30. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/mcp.py +0 -0
  31. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/proposals.py +0 -0
  32. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/reports.py +0 -0
  33. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/routers/setup.py +0 -0
  34. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/services.py +0 -0
  35. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/templates/document.html +0 -0
  36. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/api/templates/proposal.html +0 -0
  37. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/docs/agents/README.md +0 -0
  38. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/frontend/LICENSE +0 -0
  39. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/frontend/README.md +0 -0
  40. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/__init__.py +0 -0
  41. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/__init__.py +0 -0
  42. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/bank_transaction.py +0 -0
  43. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/budget.py +0 -0
  44. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/chart_of_accounts.py +0 -0
  45. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/general_ledger.py +0 -0
  46. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/journal_entry.py +0 -0
  47. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/payment_entry.py +0 -0
  48. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/pos_invoice.py +0 -0
  49. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/purchase_invoice.py +0 -0
  50. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/revaluation.py +0 -0
  51. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/sales_invoice.py +0 -0
  52. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/setup/__init__.py +0 -0
  53. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/setup/engine.py +0 -0
  54. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/setup/pack.py +0 -0
  55. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
  56. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/setup/packs/ch.py +0 -0
  57. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
  58. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
  59. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
  60. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/setup/packs/generic.py +0 -0
  61. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/setup/profiles.py +0 -0
  62. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/setup/spine.py +0 -0
  63. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/accounting/subscription.py +0 -0
  64. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/assets/__init__.py +0 -0
  65. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/assets/asset.py +0 -0
  66. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/assets/reservation.py +0 -0
  67. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/buying/__init__.py +0 -0
  68. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/buying/purchase_order.py +0 -0
  69. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/controllers/__init__.py +0 -0
  70. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/controllers/currency.py +0 -0
  71. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/controllers/defaults.py +0 -0
  72. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/controllers/pricing_rule.py +0 -0
  73. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/controllers/taxes_and_totals.py +0 -0
  74. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/database.py +0 -0
  75. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/exceptions.py +0 -0
  76. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/hooks.py +0 -0
  77. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/model.py +0 -0
  78. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/selling/__init__.py +0 -0
  79. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/selling/proposal.py +0 -0
  80. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/selling/quotation.py +0 -0
  81. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/selling/sales_order.py +0 -0
  82. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/simulation.py +0 -0
  83. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/stock/__init__.py +0 -0
  84. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/stock/delivery_note.py +0 -0
  85. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/stock/purchase_receipt.py +0 -0
  86. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/stock/stock_entry.py +0 -0
  87. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/stock/stock_ledger.py +0 -0
  88. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/lambda_erp/utils.py +0 -0
  89. {lambda_erp-0.8.16 → lambda_erp-0.8.18}/terraform/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lambda-erp
3
- Version: 0.8.16
3
+ Version: 0.8.18
4
4
  Summary: Core ERP logic - accounting, sales, purchasing, inventory
5
5
  Author: TORUS INVESTMENTS AG
6
6
  License-Expression: Apache-2.0
@@ -462,7 +462,6 @@ This is early. The project needs:
462
462
  - Country compliance packs (tax rules, invoice formats, mandatory fields)
463
463
  - Industry templates (services, retail, light manufacturing, SaaS)
464
464
  - More preset reports
465
- - A Postgres storage adapter (the current SQLite layer is fine for local evaluation but will need to be swapped for real multi-user write loads)
466
465
  - Better observability around token spend per turn
467
466
  - Native messenger integration, for WhatsApp, Telegram, etc.
468
467
 
@@ -482,13 +481,6 @@ Release notes live in [CHANGELOG.md](./CHANGELOG.md). Releases are tagged
482
481
  `vX.Y.Z` and published in lockstep to PyPI (`lambda-erp`) and npm
483
482
  (`@lambda-development/erp-core`).
484
483
 
485
- ---
486
-
487
- ## Status
488
-
489
- Version 0, and it already implements the vision: invoicing, inventory, accounting, and reporting — all driven by chat. Good for demos, internal tools, and real day-to-day work. It's under active development and improving fast.
490
-
491
- If you try it, we'd love to know what broke.
492
484
 
493
485
  ---
494
486
 
@@ -434,7 +434,6 @@ This is early. The project needs:
434
434
  - Country compliance packs (tax rules, invoice formats, mandatory fields)
435
435
  - Industry templates (services, retail, light manufacturing, SaaS)
436
436
  - More preset reports
437
- - A Postgres storage adapter (the current SQLite layer is fine for local evaluation but will need to be swapped for real multi-user write loads)
438
437
  - Better observability around token spend per turn
439
438
  - Native messenger integration, for WhatsApp, Telegram, etc.
440
439
 
@@ -454,13 +453,6 @@ Release notes live in [CHANGELOG.md](./CHANGELOG.md). Releases are tagged
454
453
  `vX.Y.Z` and published in lockstep to PyPI (`lambda-erp`) and npm
455
454
  (`@lambda-development/erp-core`).
456
455
 
457
- ---
458
-
459
- ## Status
460
-
461
- Version 0, and it already implements the vision: invoicing, inventory, accounting, and reporting — all driven by chat. Good for demos, internal tools, and real day-to-day work. It's under active development and improving fast.
462
-
463
- If you try it, we'd love to know what broke.
464
456
 
465
457
  ---
466
458
 
@@ -303,9 +303,73 @@ def update_master_record(master_type: str, name: str, data: dict) -> dict:
303
303
  # ad-hoc equality field filter (validated against the master's real columns).
304
304
  _MASTER_LIST_RESERVED = {
305
305
  "limit", "offset", "include_disabled", "search", "search_fields", "fields",
306
+ "order_by", "order",
306
307
  }
307
308
 
308
309
 
310
+ def _master_list_where(db, doctype: str, master_type: str, request: Request,
311
+ include_disabled: bool, search: str | None,
312
+ search_fields: str | None) -> tuple[list, list, set]:
313
+ """Build the validated WHERE clause shared by master list and adjacent.
314
+
315
+ Keeping this in one place is what makes detail prev/next follow the exact
316
+ search and field filters of the list the user opened the record from.
317
+ """
318
+ columns = db._get_table_columns(doctype)
319
+ where_parts: list = []
320
+ params: list = []
321
+
322
+ active = None if include_disabled else _with_active_filter(db, doctype)
323
+ if active:
324
+ wp, ps = _where_from_filters(active)
325
+ where_parts += wp
326
+ params += ps
327
+
328
+ for key, value in request.query_params.items():
329
+ if key in _MASTER_LIST_RESERVED:
330
+ continue
331
+ if key not in columns:
332
+ raise HTTPException(status_code=400, detail=f"Unknown filter field: {key}")
333
+ where_parts.append(f'"{key}" = ?')
334
+ params.append(value)
335
+
336
+ if search:
337
+ requested = [f.strip() for f in (search_fields or "").split(",") if f.strip()]
338
+ unknown = [f for f in requested if f not in columns]
339
+ if unknown:
340
+ raise HTTPException(status_code=400, detail=f"Unknown search field(s): {', '.join(unknown)}")
341
+ sf = requested or master_search_columns(db, doctype)
342
+ clause, sp = _search_clause(db, doctype, master_type, search, sf)
343
+ if clause:
344
+ where_parts.append(clause)
345
+ params += sp
346
+
347
+ return where_parts, params, columns
348
+
349
+
350
+ def _master_order(columns: set, order_by: str | None, order: str) -> tuple[str, str]:
351
+ if order_by is not None and order_by not in columns:
352
+ raise HTTPException(status_code=400, detail=f"Unknown order_by field: {order_by}")
353
+ direction = order.lower()
354
+ if direction not in ("asc", "desc"):
355
+ raise HTTPException(status_code=400, detail="order must be 'asc' or 'desc'")
356
+ # Reference masters retain their historical default: stable ID order.
357
+ return order_by or "name", direction if order_by else "asc"
358
+
359
+
360
+ def _master_order_sql(column: str, direction: str, *, reverse: bool = False) -> str:
361
+ """Deterministic master ordering with NULL values consistently at the end."""
362
+ effective = direction
363
+ nulls = "LAST"
364
+ if reverse:
365
+ effective = "desc" if direction == "asc" else "asc"
366
+ nulls = "FIRST"
367
+ sql = f'"{column}" {effective.upper()}'
368
+ if column != "name":
369
+ sql += f" NULLS {nulls}, name {effective.upper()}"
370
+ return sql
371
+
372
+
309
373
  @router.get("/{master_type}/filter-values")
310
374
  def master_filter_values(
311
375
  master_type: str,
@@ -338,52 +402,22 @@ def list_masters(
338
402
  search: str | None = None,
339
403
  search_fields: str | None = None,
340
404
  fields: str | None = None,
405
+ order_by: str | None = None,
406
+ order: str = "asc",
341
407
  _user: dict = _viewer,
342
408
  ):
343
409
  doctype, _ = _get_table(master_type)
344
410
  if not doctype:
345
411
  return {"detail": f"Unknown master type: {master_type}"}
346
412
  db = get_db()
347
- columns = db._get_table_columns(doctype)
348
-
349
- where_parts: list = []
350
- params: list = []
351
-
352
- # Active (non-disabled) filter, unless the caller opts in to disabled rows.
353
- active = None if include_disabled else _with_active_filter(db, doctype)
354
- if active:
355
- wp, ps = _where_from_filters(active)
356
- where_parts += wp
357
- params += ps
358
-
359
- # Ad-hoc equality field filters: any remaining query param naming a real
360
- # column (e.g. /masters/customer?customer_group=Commercial). Unknown field
361
- # -> 400 (a filter on a missing column is a client bug, never interpolated).
362
- for key, value in request.query_params.items():
363
- if key in _MASTER_LIST_RESERVED:
364
- continue
365
- if key not in columns:
366
- raise HTTPException(status_code=400, detail=f"Unknown filter field: {key}")
367
- where_parts.append(f'"{key}" = ?')
368
- params.append(value)
369
-
370
- # Free-text search — defaults to the master's text columns when the caller
371
- # doesn't name search_fields (so a plain ?search=... just works).
372
- if search:
373
- sf = [f.strip() for f in (search_fields or "").split(",")
374
- if f.strip() and f.strip() in columns]
375
- if not sf:
376
- sf = master_search_columns(db, doctype)
377
- clause, sp = _search_clause(db, doctype, master_type, search, sf)
378
- if clause:
379
- where_parts.append(clause)
380
- params += sp
413
+ where_parts, params, columns = _master_list_where(
414
+ db, doctype, master_type, request, include_disabled, search, search_fields,
415
+ )
416
+ sort_column, sort_direction = _master_order(columns, order_by, order)
381
417
 
382
418
  where = (" WHERE " + " AND ".join(where_parts)) if where_parts else ""
383
419
  total = count_query_cached(f'SELECT COUNT(*) as c FROM "{doctype}"{where}', params)
384
420
 
385
- # Deterministic order by the `name` PK (already indexed) — required for
386
- # OFFSET pagination and the /adjacent prev/next contract.
387
421
  requested_fields = [f.strip() for f in (fields or "").split(",") if f.strip()]
388
422
  if requested_fields:
389
423
  unknown = [f for f in requested_fields if f not in columns]
@@ -394,7 +428,10 @@ def list_masters(
394
428
  projection = ", ".join(f'"{f}"' for f in requested_fields)
395
429
  else:
396
430
  projection = "*"
397
- query = f'SELECT {projection} FROM "{doctype}"{where} ORDER BY name LIMIT {int(limit)}'
431
+ query = (
432
+ f'SELECT {projection} FROM "{doctype}"{where} '
433
+ f'ORDER BY {_master_order_sql(sort_column, sort_direction)} LIMIT {int(limit)}'
434
+ )
398
435
  if offset:
399
436
  query += f" OFFSET {int(offset)}"
400
437
  rows = db.sql(query, params)
@@ -424,26 +461,79 @@ def search_masters(master_type: str, q: str = "", _user: dict = _viewer):
424
461
 
425
462
 
426
463
  @router.get("/{master_type}/{name}/adjacent")
427
- def adjacent_master(master_type: str, name: str, include_disabled: bool = True,
428
- _user: dict = _viewer):
429
- """The records immediately before/after `name` in the list's order (name
430
- ASC) — {"prev": name|None, "next": name|None}. Keyset queries on the
431
- primary key. include_disabled defaults True to step 1:1 with the master
432
- list, which shows disabled records too."""
464
+ def adjacent_master(
465
+ master_type: str,
466
+ name: str,
467
+ request: Request,
468
+ include_disabled: bool = False,
469
+ search: str | None = None,
470
+ search_fields: str | None = None,
471
+ order_by: str | None = None,
472
+ order: str = "asc",
473
+ _user: dict = _viewer,
474
+ ):
475
+ """Records immediately before/after ``name`` in the current master list.
476
+
477
+ Search, equality filters, disabled visibility, and ordering are shared with
478
+ ``list_masters``. Keyset predicates keep navigation proportional to one
479
+ index lookup rather than numbering the entire result set.
480
+ """
433
481
  doctype, _ = _get_table(master_type)
434
482
  if not doctype:
435
483
  raise HTTPException(status_code=404, detail=f"Unknown master type: {master_type}")
436
484
  db = get_db()
437
- active = ("" if include_disabled or "disabled" not in db._get_table_columns(doctype)
438
- else "disabled = 0 AND ")
485
+ where_parts, params, columns = _master_list_where(
486
+ db, doctype, master_type, request, include_disabled, search, search_fields,
487
+ )
488
+ sort_column, sort_direction = _master_order(columns, order_by, order)
489
+ base_where = (" AND ".join(where_parts)) if where_parts else "1 = 1"
490
+ current_rows = db.sql(
491
+ f'SELECT "{sort_column}" AS sort_value FROM "{doctype}" '
492
+ f"WHERE {base_where} AND name = ? LIMIT 1",
493
+ [*params, name],
494
+ )
495
+ if not current_rows:
496
+ return {"prev": None, "next": None}
497
+ current_value = current_rows[0]["sort_value"]
498
+
499
+ def neighbor(toward: str):
500
+ after = toward == "next"
501
+ same_cmp = ">" if sort_direction == "asc" else "<"
502
+ primary_cmp = same_cmp
503
+ if not after:
504
+ same_cmp = "<" if same_cmp == ">" else ">"
505
+ primary_cmp = "<" if primary_cmp == ">" else ">"
506
+
507
+ if sort_column == "name":
508
+ keyset = f'name {same_cmp} ?'
509
+ key_params = [name]
510
+ elif current_value is None:
511
+ if after:
512
+ keyset = f'"{sort_column}" IS NULL AND name {same_cmp} ?'
513
+ else:
514
+ keyset = (
515
+ f'("{sort_column}" IS NOT NULL OR '
516
+ f'("{sort_column}" IS NULL AND name {same_cmp} ?))'
517
+ )
518
+ key_params = [name]
519
+ else:
520
+ keyset = (
521
+ f'("{sort_column}" {primary_cmp} ? OR '
522
+ f'("{sort_column}" = ? AND name {same_cmp} ?)'
523
+ )
524
+ key_params = [current_value, current_value, name]
525
+ if after:
526
+ keyset += f' OR "{sort_column}" IS NULL'
527
+ keyset += ")"
439
528
 
440
- def neighbor(cmp: str, direction: str):
441
529
  rows = db.sql(
442
- f'SELECT name FROM "{doctype}" WHERE {active}name {cmp} ? '
443
- f"ORDER BY name {direction} LIMIT 1", [name])
530
+ f'SELECT name FROM "{doctype}" WHERE {base_where} AND {keyset} '
531
+ f'ORDER BY {_master_order_sql(sort_column, sort_direction, reverse=not after)} LIMIT 1',
532
+ [*params, *key_params],
533
+ )
444
534
  return rows[0]["name"] if rows else None
445
535
 
446
- return {"prev": neighbor("<", "DESC"), "next": neighbor(">", "ASC")}
536
+ return {"prev": neighbor("prev"), "next": neighbor("next")}
447
537
 
448
538
 
449
539
  @router.get("/{master_type}/{name}")
@@ -180,10 +180,10 @@ export const api = {
180
180
  `/documents/${doctype}/${encodeURIComponent(name)}/adjacent${qs(params)}`,
181
181
  ),
182
182
 
183
- // Prev/next master record around `name` in the master list's order (name ASC).
184
- adjacentMaster: (masterType: string, name: string) =>
183
+ // Prev/next master record around `name` in the same order+filters as its list.
184
+ adjacentMaster: (masterType: string, name: string, params?: Record<string, string | number | undefined>) =>
185
185
  request<{ prev: string | null; next: string | null }>(
186
- `/masters/${masterType}/${encodeURIComponent(name)}/adjacent`,
186
+ `/masters/${masterType}/${encodeURIComponent(name)}/adjacent${qs(params)}`,
187
187
  ),
188
188
 
189
189
  createDocument: (doctype: string, data: any) =>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lambda-erp"
3
- version = "0.8.16"
3
+ version = "0.8.18"
4
4
  description = "Core ERP logic - accounting, sales, purchasing, inventory"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
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