lambda-erp 0.6.5__tar.gz → 0.6.7__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 (83) hide show
  1. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/PKG-INFO +1 -1
  2. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/documents.py +13 -0
  3. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/services.py +23 -4
  4. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/utils.py +10 -3
  5. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/pyproject.toml +1 -1
  6. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/.gitignore +0 -0
  7. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/LICENSE +0 -0
  8. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/README.md +0 -0
  9. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/__init__.py +0 -0
  10. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/attachments.py +0 -0
  11. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/auth.py +0 -0
  12. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/bootstrap.py +0 -0
  13. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/chat.py +0 -0
  14. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/demo_limits.py +0 -0
  15. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/deps.py +0 -0
  16. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/errors.py +0 -0
  17. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/main.py +0 -0
  18. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/oauth.py +0 -0
  19. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/pdf.py +0 -0
  20. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/providers.py +0 -0
  21. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/remarks_md.py +0 -0
  22. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/__init__.py +0 -0
  23. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/accounting.py +0 -0
  24. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/admin.py +0 -0
  25. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/analytics.py +0 -0
  26. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/bank_reconciliation.py +0 -0
  27. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/chat_api.py +0 -0
  28. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/masters.py +0 -0
  29. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/proposals.py +0 -0
  30. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/reports.py +0 -0
  31. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/setup.py +0 -0
  32. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/templates/document.html +0 -0
  33. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/templates/proposal.html +0 -0
  34. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/docs/agents/README.md +0 -0
  35. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/frontend/LICENSE +0 -0
  36. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/frontend/README.md +0 -0
  37. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/frontend/src/api/client.ts +0 -0
  38. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/__init__.py +0 -0
  39. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/__init__.py +0 -0
  40. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/bank_transaction.py +0 -0
  41. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/budget.py +0 -0
  42. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/chart_of_accounts.py +0 -0
  43. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/general_ledger.py +0 -0
  44. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/journal_entry.py +0 -0
  45. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/payment_entry.py +0 -0
  46. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/pos_invoice.py +0 -0
  47. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/purchase_invoice.py +0 -0
  48. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/revaluation.py +0 -0
  49. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/sales_invoice.py +0 -0
  50. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/__init__.py +0 -0
  51. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/engine.py +0 -0
  52. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/pack.py +0 -0
  53. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
  54. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/ch.py +0 -0
  55. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
  56. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
  57. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
  58. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/generic.py +0 -0
  59. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/profiles.py +0 -0
  60. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/spine.py +0 -0
  61. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/subscription.py +0 -0
  62. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/buying/__init__.py +0 -0
  63. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/buying/purchase_order.py +0 -0
  64. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/controllers/__init__.py +0 -0
  65. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/controllers/currency.py +0 -0
  66. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/controllers/defaults.py +0 -0
  67. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/controllers/pricing_rule.py +0 -0
  68. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/controllers/taxes_and_totals.py +0 -0
  69. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/database.py +0 -0
  70. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/exceptions.py +0 -0
  71. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/hooks.py +0 -0
  72. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/model.py +0 -0
  73. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/selling/__init__.py +0 -0
  74. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/selling/proposal.py +0 -0
  75. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/selling/quotation.py +0 -0
  76. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/selling/sales_order.py +0 -0
  77. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/simulation.py +0 -0
  78. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/stock/__init__.py +0 -0
  79. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/stock/delivery_note.py +0 -0
  80. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/stock/purchase_receipt.py +0 -0
  81. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/stock/stock_entry.py +0 -0
  82. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/stock/stock_ledger.py +0 -0
  83. {lambda_erp-0.6.5 → lambda_erp-0.6.7}/terraform/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lambda-erp
3
- Version: 0.6.5
3
+ Version: 0.6.7
4
4
  Summary: Core ERP logic - accounting, sales, purchasing, inventory
5
5
  Author: TORUS INVESTMENTS AG
6
6
  License-Expression: Apache-2.0
@@ -28,6 +28,7 @@ _manager = Depends(require_role("manager"))
28
28
  _LIST_RESERVED = {
29
29
  "status", "party", "from_date", "to_date", "docstatus",
30
30
  "include_discarded", "limit", "offset", "order_by", "order",
31
+ "date_field",
31
32
  }
32
33
 
33
34
 
@@ -43,6 +44,7 @@ def list_docs(
43
44
  include_discarded: bool = False,
44
45
  order_by: str | None = None,
45
46
  order: str = "desc",
47
+ date_field: str | None = None,
46
48
  limit: int = Query(default=50, le=500),
47
49
  offset: int = Query(default=0, ge=0),
48
50
  _user: dict = _viewer,
@@ -70,6 +72,14 @@ def list_docs(
70
72
  raise HTTPException(status_code=400, detail=f"Unknown filter field: {key}")
71
73
  filters[key] = value
72
74
 
75
+ # Which column from_date/to_date filter on. The frontend passes its declared
76
+ # dateField so plugin doctypes get working date filters without a server-side
77
+ # map. Passed through untouched here; list_documents ignores it unless it's a
78
+ # real column (so a config with a synthetic dateField degrades to no date
79
+ # filter rather than 400-ing the whole list), and never interpolates it blind.
80
+ if date_field:
81
+ filters["date_field"] = date_field
82
+
73
83
  if order_by is not None and order_by not in columns:
74
84
  raise HTTPException(status_code=400, detail=f"Unknown order_by field: {order_by}")
75
85
  if order.lower() not in ("asc", "desc"):
@@ -94,6 +104,7 @@ def adjacent_doc(
94
104
  include_discarded: bool = False,
95
105
  order_by: str | None = None,
96
106
  order: str = "desc",
107
+ date_field: str | None = None,
97
108
  _user: dict = _viewer,
98
109
  ):
99
110
  """Prev/next record around `name` in the same order+filters the list uses.
@@ -117,6 +128,8 @@ def adjacent_doc(
117
128
  if key not in columns:
118
129
  raise HTTPException(status_code=400, detail=f"Unknown filter field: {key}")
119
130
  filters[key] = value
131
+ if date_field:
132
+ filters["date_field"] = date_field
120
133
  if order_by is not None and order_by not in columns:
121
134
  raise HTTPException(status_code=400, detail=f"Unknown order_by field: {order_by}")
122
135
  if order.lower() not in ("asc", "desc"):
@@ -392,6 +392,17 @@ DATE_FIELDS = {
392
392
  }
393
393
 
394
394
 
395
+ def _resolve_date_field(db, doctype: str, override: str | None) -> str | None:
396
+ """The column that from_date/to_date range-filter against. An explicit
397
+ override (the frontend's declared `dateField`, passed as the `date_field`
398
+ filter) wins when it names a real column; otherwise fall back to the
399
+ built-in DATE_FIELDS map for core doctypes. Guarded so the override is never
400
+ interpolated into SQL unless it's an actual column."""
401
+ if override and override in db._get_table_columns(doctype):
402
+ return override
403
+ return DATE_FIELDS.get(doctype)
404
+
405
+
395
406
  def _exclude_discarded(db, doctype: str, db_filters: dict, include_discarded: bool) -> None:
396
407
  """Hide voided drafts (discarded=1) unless explicitly requested. Guarded on
397
408
  the column existing so non-submittable doctypes are unaffected."""
@@ -421,6 +432,7 @@ def list_documents(doctype_slug: str, filters: dict = None, limit: int = 50, off
421
432
  db_filters = {}
422
433
  from_date = None
423
434
  to_date = None
435
+ date_field_override = None
424
436
  if filters:
425
437
  for key, value in filters.items():
426
438
  if value is None or value == "":
@@ -429,13 +441,15 @@ def list_documents(doctype_slug: str, filters: dict = None, limit: int = 50, off
429
441
  from_date = value
430
442
  elif key == "to_date":
431
443
  to_date = value
444
+ elif key == "date_field":
445
+ date_field_override = value
432
446
  else:
433
447
  db_filters[key] = value
434
448
 
435
449
  _exclude_discarded(db, doctype, db_filters, include_discarded)
436
450
 
437
451
  # Date range filtering via the doctype's primary date field
438
- date_field = DATE_FIELDS.get(doctype)
452
+ date_field = _resolve_date_field(db, doctype, date_field_override)
439
453
  if date_field:
440
454
  if from_date:
441
455
  db_filters[date_field] = (">=", from_date)
@@ -481,6 +495,7 @@ def count_documents(doctype_slug: str, filters: dict = None, include_discarded:
481
495
  db_filters = {}
482
496
  from_date = None
483
497
  to_date = None
498
+ date_field_override = None
484
499
  if filters:
485
500
  for key, value in filters.items():
486
501
  if value is None or value == "":
@@ -489,12 +504,14 @@ def count_documents(doctype_slug: str, filters: dict = None, include_discarded:
489
504
  from_date = value
490
505
  elif key == "to_date":
491
506
  to_date = value
507
+ elif key == "date_field":
508
+ date_field_override = value
492
509
  else:
493
510
  db_filters[key] = value
494
511
 
495
512
  _exclude_discarded(db, doctype, db_filters, include_discarded)
496
513
 
497
- date_field = DATE_FIELDS.get(doctype)
514
+ date_field = _resolve_date_field(db, doctype, date_field_override)
498
515
  where_parts = []
499
516
  params = []
500
517
  if date_field and from_date:
@@ -524,7 +541,7 @@ def _filter_where(db, doctype: str, filters: dict, include_discarded: bool):
524
541
  (equality filters + from_date/to_date on the doctype's date field + discard
525
542
  exclusion). Same semantics as list_documents/count_documents so prev/next
526
543
  matches exactly what the list shows."""
527
- db_filters, from_date, to_date = {}, None, None
544
+ db_filters, from_date, to_date, date_field_override = {}, None, None, None
528
545
  for key, value in (filters or {}).items():
529
546
  if value is None or value == "":
530
547
  continue
@@ -532,11 +549,13 @@ def _filter_where(db, doctype: str, filters: dict, include_discarded: bool):
532
549
  from_date = value
533
550
  elif key == "to_date":
534
551
  to_date = value
552
+ elif key == "date_field":
553
+ date_field_override = value
535
554
  else:
536
555
  db_filters[key] = value
537
556
  _exclude_discarded(db, doctype, db_filters, include_discarded)
538
557
 
539
- date_field = DATE_FIELDS.get(doctype)
558
+ date_field = _resolve_date_field(db, doctype, date_field_override)
540
559
  where_parts, params = [], []
541
560
  if date_field and from_date:
542
561
  where_parts.append(f'"{date_field}" >= ?'); params.append(from_date)
@@ -6,7 +6,7 @@ throughout the the reference implementation business logic.
6
6
  """
7
7
 
8
8
  import math
9
- from datetime import date, datetime, timedelta
9
+ from datetime import date, datetime, timedelta, timezone
10
10
  from decimal import Decimal, ROUND_HALF_UP
11
11
 
12
12
  def flt(value, precision=None):
@@ -54,8 +54,15 @@ def nowdate():
54
54
  return date.today().isoformat()
55
55
 
56
56
  def now():
57
- """Return current datetime as string."""
58
- return datetime.now().isoformat()
57
+ """Return the current instant as a timezone-aware UTC ISO string
58
+ (e.g. "2026-07-29T09:57:00.123456+00:00").
59
+
60
+ The offset is deliberate: a naive isoformat() (no zone) is ambiguous, and a
61
+ client parsing it (JS `new Date(...)`) reads it as *browser-local*, so a
62
+ timestamp stamped on a UTC server renders shifted by the viewer's UTC
63
+ offset. Stamping the zone makes every stored timestamp (creation, modified,
64
+ occurred_at, …) an unambiguous instant that round-trips correctly."""
65
+ return datetime.now(timezone.utc).isoformat()
59
66
 
60
67
  def add_days(dt, days):
61
68
  """Add days to a date."""
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lambda-erp"
3
- version = "0.6.5"
3
+ version = "0.6.7"
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
File without changes
File without changes
File without changes
File without changes