@devx-retailos/cms 0.0.1 → 0.1.0

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 (30) hide show
  1. package/.medusa/server/src/api/admin/retailos/cms/accumulation/[storeId]/route.js +3 -5
  2. package/.medusa/server/src/api/admin/retailos/cms/export/route.js +2 -2
  3. package/.medusa/server/src/api/admin/retailos/cms/handovers/[id]/route.js +2 -2
  4. package/.medusa/server/src/api/admin/retailos/cms/handovers/route.js +72 -7
  5. package/.medusa/server/src/api/admin/retailos/cms/petty-cash/route.js +35 -4
  6. package/.medusa/server/src/api/admin/retailos/cms/reconciliation/route.js +42 -0
  7. package/.medusa/server/src/modules/cms/migrations/Migration20260626000000.js +16 -0
  8. package/.medusa/server/src/modules/cms/models/cms-handover.js +3 -1
  9. package/.medusa/server/src/modules/cms/permissions.js +6 -1
  10. package/.medusa/server/src/modules/cms/services/cms-module-service.js +137 -42
  11. package/README.md +116 -0
  12. package/package.json +3 -3
  13. package/src/api/admin/retailos/cms/accumulation/[storeId]/__tests__/route.test.ts +27 -9
  14. package/src/api/admin/retailos/cms/accumulation/[storeId]/route.ts +2 -4
  15. package/src/api/admin/retailos/cms/export/__tests__/route.test.ts +9 -9
  16. package/src/api/admin/retailos/cms/export/route.ts +1 -1
  17. package/src/api/admin/retailos/cms/handovers/[id]/__tests__/route.test.ts +5 -5
  18. package/src/api/admin/retailos/cms/handovers/[id]/route.ts +1 -1
  19. package/src/api/admin/retailos/cms/handovers/__tests__/route.test.ts +22 -11
  20. package/src/api/admin/retailos/cms/handovers/route.ts +78 -6
  21. package/src/api/admin/retailos/cms/petty-cash/__tests__/route.test.ts +18 -12
  22. package/src/api/admin/retailos/cms/petty-cash/route.ts +46 -4
  23. package/src/api/admin/retailos/cms/reconciliation/__tests__/route.test.ts +124 -0
  24. package/src/api/admin/retailos/cms/reconciliation/route.ts +47 -0
  25. package/src/modules/cms/__tests__/cms-module-service.test.ts +219 -78
  26. package/src/modules/cms/__tests__/permissions.test.ts +3 -2
  27. package/src/modules/cms/migrations/Migration20260626000000.ts +17 -0
  28. package/src/modules/cms/models/cms-handover.ts +2 -0
  29. package/src/modules/cms/permissions.ts +5 -0
  30. package/src/modules/cms/services/cms-module-service.ts +217 -41
@@ -3,7 +3,7 @@ import { GET } from "../route"
3
3
 
4
4
  function makeService() {
5
5
  return {
6
- retrieveCmsHandover: vi.fn(),
6
+ retrieveRetailosCmsHandover: vi.fn(),
7
7
  }
8
8
  }
9
9
 
@@ -31,20 +31,20 @@ describe("GET /handovers/[id]", () => {
31
31
  it("returns handover when found", async () => {
32
32
  const service = makeService()
33
33
  const handover = { id: "cmsh_1", store_id: "s1", handover_amount: 500 }
34
- service.retrieveCmsHandover.mockResolvedValue(handover)
34
+ service.retrieveRetailosCmsHandover.mockResolvedValue(handover)
35
35
  const req = makeReq({ id: "cmsh_1" }, service)
36
36
  const res = makeRes()
37
37
 
38
38
  await GET(req as any, res as any)
39
39
 
40
- expect(service.retrieveCmsHandover).toHaveBeenCalledWith("cmsh_1")
40
+ expect(service.retrieveRetailosCmsHandover).toHaveBeenCalledWith("cmsh_1")
41
41
  expect(res.status).toHaveBeenCalledWith(200)
42
42
  expect(res.json).toHaveBeenCalledWith({ handover })
43
43
  })
44
44
 
45
45
  it("returns 404 when handover is null", async () => {
46
46
  const service = makeService()
47
- service.retrieveCmsHandover.mockResolvedValue(null)
47
+ service.retrieveRetailosCmsHandover.mockResolvedValue(null)
48
48
  const req = makeReq({ id: "cmsh_missing" }, service)
49
49
  const res = makeRes()
50
50
 
@@ -56,7 +56,7 @@ describe("GET /handovers/[id]", () => {
56
56
 
57
57
  it("returns 500 when service throws", async () => {
58
58
  const service = makeService()
59
- service.retrieveCmsHandover.mockRejectedValue(new Error("db error"))
59
+ service.retrieveRetailosCmsHandover.mockRejectedValue(new Error("db error"))
60
60
  const req = makeReq({ id: "cmsh_1" }, service)
61
61
  const res = makeRes()
62
62
 
@@ -21,7 +21,7 @@ export const GET = async (req: AuthenticatedMedusaRequest, res: MedusaResponse)
21
21
  const { id } = req.params
22
22
  try {
23
23
  const service = getService(req)
24
- const handover = await service.retrieveCmsHandover(id)
24
+ const handover = await service.retrieveRetailosCmsHandover(id)
25
25
  if (!handover) {
26
26
  return res.status(404).json({ code: "NOT_FOUND", message: "Handover not found" })
27
27
  }
@@ -3,18 +3,24 @@ import { GET, POST } from "../route"
3
3
 
4
4
  function makeService() {
5
5
  return {
6
- listAndCountCmsHandovers: vi.fn().mockResolvedValue([[], 0]),
7
6
  handover: vi.fn().mockResolvedValue({ id: "cmsh_1", store_id: "s1", handover_amount: 100 }),
8
7
  }
9
8
  }
10
9
 
11
- function makeReq(overrides: Record<string, unknown> = {}, service = makeService()) {
10
+ function makeQueryModule(data: unknown[] = [], count = 0) {
11
+ return {
12
+ graph: vi.fn().mockResolvedValue({ data, metadata: { count } }),
13
+ }
14
+ }
15
+
16
+ function makeReq(overrides: Record<string, unknown> = {}, service = makeService(), queryModule = makeQueryModule()) {
12
17
  return {
13
18
  body: {},
14
- filterableFields: {},
19
+ query: {},
15
20
  scope: {
16
21
  resolve: vi.fn().mockImplementation((key: string) => {
17
22
  if (key === "cms") return service
23
+ if (key === "query") return queryModule
18
24
  throw new Error("not found")
19
25
  }),
20
26
  },
@@ -32,22 +38,27 @@ function makeRes() {
32
38
 
33
39
  describe("GET /handovers", () => {
34
40
  it("returns handover list with count", async () => {
41
+ const data = [{ id: "cmsh_1" }, { id: "cmsh_2" }]
42
+ const queryModule = makeQueryModule(data, 2)
35
43
  const service = makeService()
36
- const handovers = [{ id: "cmsh_1" }, { id: "cmsh_2" }]
37
- service.listAndCountCmsHandovers.mockResolvedValue([handovers, 2])
38
- const req = makeReq({}, service)
44
+ const req = makeReq({}, service, queryModule)
39
45
  const res = makeRes()
40
46
 
41
47
  await GET(req as any, res as any)
42
48
 
43
49
  expect(res.status).toHaveBeenCalledWith(200)
44
- expect(res.json).toHaveBeenCalledWith({ handovers, count: 2 })
50
+ expect(res.json).toHaveBeenCalledWith({
51
+ handovers: [
52
+ { id: "cmsh_1", employee: null, store: null },
53
+ { id: "cmsh_2", employee: null, store: null },
54
+ ],
55
+ count: 2,
56
+ })
45
57
  })
46
58
 
47
- it("returns 500 when service throws", async () => {
48
- const service = makeService()
49
- service.listAndCountCmsHandovers.mockRejectedValue(new Error("db error"))
50
- const req = makeReq({}, service)
59
+ it("returns 500 when query.graph throws", async () => {
60
+ const queryModule = { graph: vi.fn().mockRejectedValue(new Error("db error")) }
61
+ const req = makeReq({}, makeService(), queryModule)
51
62
  const res = makeRes()
52
63
 
53
64
  await GET(req as any, res as any)
@@ -2,6 +2,7 @@ import type {
2
2
  AuthenticatedMedusaRequest,
3
3
  MedusaResponse,
4
4
  } from "@medusajs/framework"
5
+ import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
5
6
  import { z } from "zod"
6
7
  import { CMS_MODULE } from "../../../../../modules/cms/constants"
7
8
  import type CmsModuleService from "../../../../../modules/cms/services/cms-module-service"
@@ -32,13 +33,84 @@ function getLogger(req: AuthenticatedMedusaRequest) {
32
33
 
33
34
  export const GET = async (req: AuthenticatedMedusaRequest, res: MedusaResponse) => {
34
35
  try {
35
- const service = getService(req)
36
- const filters = (req.filterableFields ?? {}) as Record<string, unknown>
37
- const [data, count] = await service.listAndCountCmsHandovers(filters, {
38
- take: 100,
39
- order: { created_at: "DESC" },
36
+ const query = (req as any).scope.resolve(ContainerRegistrationKeys.QUERY)
37
+ const q = req.query as Record<string, any>
38
+
39
+ const filters: Record<string, unknown> = {}
40
+
41
+ // Direct equality filters from query params
42
+ const DIRECT = ["store_id", "employee_id", "type"] as const
43
+ for (const key of DIRECT) {
44
+ if (q[key] !== undefined && q[key] !== "") filters[key] = q[key]
45
+ }
46
+ if (q.handover_id) filters.handover_id = q.handover_id
47
+
48
+ // q: full-text search on handover_id
49
+ if (q.q) {
50
+ filters.handover_id = { $ilike: `%${q.q}%` }
51
+ }
52
+
53
+ // Date range filters
54
+ if (q.created_at) {
55
+ filters.created_at = typeof q.created_at === "object" ? q.created_at : { $gte: new Date(q.created_at as string) }
56
+ }
57
+ if (q.updated_at) {
58
+ filters.updated_at = typeof q.updated_at === "object" ? q.updated_at : { $gte: new Date(q.updated_at as string) }
59
+ }
60
+
61
+ // Pagination
62
+ const limit = q.limit ? Math.min(parseInt(q.limit as string, 10), 500) : 20
63
+ const offset = q.offset ? parseInt(q.offset as string, 10) : 0
64
+
65
+ // Ordering: "field" = ASC, "-field" = DESC
66
+ let order: Record<string, "ASC" | "DESC"> = { created_at: "DESC" }
67
+ if (q.order) {
68
+ const raw = q.order as string
69
+ const isDesc = raw.startsWith("-")
70
+ const field = isDesc ? raw.slice(1) : raw
71
+ const SORTABLE = ["created_at", "updated_at", "handover_amount", "total_cash"]
72
+ if (SORTABLE.includes(field)) {
73
+ order = { [field]: isDesc ? "DESC" : "ASC" }
74
+ }
75
+ }
76
+
77
+ const { data, metadata } = await query.graph({
78
+ entity: "retailos_cms_handover",
79
+ fields: [
80
+ "*",
81
+ "retailos_employee.id",
82
+ "retailos_employee.first_name",
83
+ "retailos_employee.last_name",
84
+ "retailos_employee.email",
85
+ "retailos_store.id",
86
+ "retailos_store.name",
87
+ "retailos_store.store_code",
88
+ ],
89
+ filters: filters as any,
90
+ pagination: { skip: offset, take: limit },
91
+ // @ts-ignore
92
+ order,
40
93
  })
41
- return res.status(200).json({ handovers: data, count })
94
+
95
+ const handovers = (data as any[]).map((h: any) => ({
96
+ ...h,
97
+ employee: h.retailos_employee
98
+ ? {
99
+ id: h.retailos_employee.id,
100
+ first_name: h.retailos_employee.first_name,
101
+ last_name: h.retailos_employee.last_name,
102
+ }
103
+ : null,
104
+ store: h.retailos_store
105
+ ? {
106
+ id: h.retailos_store.id,
107
+ name: h.retailos_store.name,
108
+ store_code: h.retailos_store.store_code,
109
+ }
110
+ : null,
111
+ }))
112
+
113
+ return res.status(200).json({ handovers, count: (metadata as any)?.count ?? handovers.length })
42
114
  } catch (err) {
43
115
  getLogger(req).error("[retailos/cms] list handovers failed", {
44
116
  error: err instanceof Error ? err.message : String(err),
@@ -3,13 +3,13 @@ import { GET } from "../route"
3
3
 
4
4
  function makeService() {
5
5
  return {
6
- listAndCountPettyCashTransactions: vi.fn().mockResolvedValue([[], 0]),
6
+ listAndCountRetailosPettyCashTransactions: vi.fn().mockResolvedValue([[], 0]),
7
7
  }
8
8
  }
9
9
 
10
- function makeReq(filterableFields: Record<string, unknown> = {}, service = makeService()) {
10
+ function makeReq(query: Record<string, unknown> = {}, service = makeService()) {
11
11
  return {
12
- filterableFields,
12
+ query,
13
13
  scope: {
14
14
  resolve: vi.fn().mockImplementation((key: string) => {
15
15
  if (key === "cms") return service
@@ -28,32 +28,38 @@ function makeRes() {
28
28
  }
29
29
 
30
30
  describe("GET /petty-cash", () => {
31
- it("returns transactions and count", async () => {
31
+ it("returns petty cash transactions and count", async () => {
32
32
  const service = makeService()
33
33
  const transactions = [{ id: "pct_1" }, { id: "pct_2" }]
34
- service.listAndCountPettyCashTransactions.mockResolvedValue([transactions, 2])
34
+ service.listAndCountRetailosPettyCashTransactions.mockResolvedValue([transactions, 2])
35
35
  const req = makeReq({}, service)
36
36
  const res = makeRes()
37
37
 
38
38
  await GET(req as any, res as any)
39
39
 
40
- expect(service.listAndCountPettyCashTransactions).toHaveBeenCalledWith(
40
+ expect(service.listAndCountRetailosPettyCashTransactions).toHaveBeenCalledWith(
41
41
  expect.objectContaining({ transaction_type: ["open", "close", "petty_cash"] }),
42
42
  expect.objectContaining({ take: 100 })
43
43
  )
44
44
  expect(res.status).toHaveBeenCalledWith(200)
45
- expect(res.json).toHaveBeenCalledWith({ transactions, count: 2 })
45
+ expect(res.json).toHaveBeenCalledWith({
46
+ petty_cash: [
47
+ { id: "pct_1", employee: null },
48
+ { id: "pct_2", employee: null },
49
+ ],
50
+ count: 2,
51
+ })
46
52
  })
47
53
 
48
- it("merges filterableFields with transaction_type filter", async () => {
54
+ it("includes store_id in filter when provided", async () => {
49
55
  const service = makeService()
50
- service.listAndCountPettyCashTransactions.mockResolvedValue([[], 0])
51
- const req = makeReq({ store_id: ["s1"] }, service)
56
+ service.listAndCountRetailosPettyCashTransactions.mockResolvedValue([[], 0])
57
+ const req = makeReq({ store_id: "s1" }, service)
52
58
  const res = makeRes()
53
59
 
54
60
  await GET(req as any, res as any)
55
61
 
56
- expect(service.listAndCountPettyCashTransactions).toHaveBeenCalledWith(
62
+ expect(service.listAndCountRetailosPettyCashTransactions).toHaveBeenCalledWith(
57
63
  expect.objectContaining({ store_id: ["s1"], transaction_type: ["open", "close", "petty_cash"] }),
58
64
  expect.any(Object)
59
65
  )
@@ -61,7 +67,7 @@ describe("GET /petty-cash", () => {
61
67
 
62
68
  it("returns 500 when service throws", async () => {
63
69
  const service = makeService()
64
- service.listAndCountPettyCashTransactions.mockRejectedValue(new Error("db error"))
70
+ service.listAndCountRetailosPettyCashTransactions.mockRejectedValue(new Error("db error"))
65
71
  const req = makeReq({}, service)
66
72
  const res = makeRes()
67
73
 
@@ -5,6 +5,8 @@ import type {
5
5
  import { CMS_MODULE } from "../../../../../modules/cms/constants"
6
6
  import type CmsModuleService from "../../../../../modules/cms/services/cms-module-service"
7
7
 
8
+ const EMPLOYEE_MODULE = "employee"
9
+
8
10
  function getService(req: AuthenticatedMedusaRequest): CmsModuleService {
9
11
  return (req as any).scope.resolve(CMS_MODULE)
10
12
  }
@@ -20,12 +22,52 @@ function getLogger(req: AuthenticatedMedusaRequest) {
20
22
  export const GET = async (req: AuthenticatedMedusaRequest, res: MedusaResponse) => {
21
23
  try {
22
24
  const service = getService(req)
23
- const filters = (req.filterableFields ?? {}) as Record<string, unknown>
24
- const [transactions, count] = await service.listAndCountPettyCashTransactions(
25
+ const q = req.query as Record<string, any>
26
+
27
+ const filters: Record<string, unknown> = {}
28
+ if (q.store_id) filters.store_id = [q.store_id]
29
+
30
+ const limit = q.limit ? Math.min(parseInt(q.limit as string, 10), 500) : 100
31
+ const offset = q.offset ? parseInt(q.offset as string, 10) : 0
32
+
33
+ const [transactions, count] = await service.listAndCountRetailosPettyCashTransactions(
25
34
  { ...filters, transaction_type: ["open", "close", "petty_cash"] },
26
- { take: 100, order: { created_at: "DESC" } }
35
+ { take: limit, skip: offset, order: { created_at: "DESC" } } as any
27
36
  )
28
- return res.status(200).json({ transactions, count })
37
+
38
+ // Enrich with employee data via bulk lookup
39
+ const employeeIds = [...new Set(
40
+ transactions.map((t: any) => t.employee_id).filter(Boolean)
41
+ )] as string[]
42
+
43
+ let employeeMap = new Map<string, { id: string; first_name: string; last_name: string }>()
44
+ if (employeeIds.length) {
45
+ try {
46
+ const employeeService = (req as any).scope.resolve(EMPLOYEE_MODULE)
47
+ const employees = await employeeService.listEmployees(
48
+ { id: employeeIds },
49
+ { select: ["id", "first_name", "last_name"] }
50
+ )
51
+ employeeMap = new Map(employees.map((e: any) => [e.id, e]))
52
+ } catch {
53
+ // employee module unavailable — return without enrichment
54
+ }
55
+ }
56
+
57
+ const petty_cash = transactions.map((t: any) => ({
58
+ ...t,
59
+ employee: t.employee_id
60
+ ? (employeeMap.get(t.employee_id)
61
+ ? {
62
+ id: employeeMap.get(t.employee_id)!.id,
63
+ first_name: employeeMap.get(t.employee_id)!.first_name,
64
+ last_name: employeeMap.get(t.employee_id)!.last_name,
65
+ }
66
+ : null)
67
+ : null,
68
+ }))
69
+
70
+ return res.status(200).json({ petty_cash, count })
29
71
  } catch (err) {
30
72
  getLogger(req).error("[retailos/cms] list petty cash failed", {
31
73
  error: err instanceof Error ? err.message : String(err),
@@ -0,0 +1,124 @@
1
+ import { describe, it, expect, vi } from "vitest"
2
+ import { GET } from "../route"
3
+
4
+ function makeReport(overrides = {}) {
5
+ return {
6
+ date: "2026-06-23",
7
+ store_id: "s1",
8
+ opening_balance: 8200,
9
+ net_cash_sales: 28450,
10
+ total_handovers_in: 0,
11
+ total_handovers_out: 5000,
12
+ petty_cash_in: 0,
13
+ petty_cash_out: 1000,
14
+ expected_closing_balance: 30650,
15
+ closing_balance: 30650,
16
+ cms_balance: 30650,
17
+ variance: 0,
18
+ is_balanced: true,
19
+ handovers: [],
20
+ shift_logs: [],
21
+ ...overrides,
22
+ }
23
+ }
24
+
25
+ function makeService(report = makeReport()) {
26
+ return { getReconciliationReport: vi.fn().mockResolvedValue(report) }
27
+ }
28
+
29
+ function makeReq(query: Record<string, string> = {}, service = makeService()) {
30
+ return {
31
+ query,
32
+ scope: {
33
+ resolve: vi.fn().mockImplementation((key: string) => {
34
+ if (key === "cms") return service
35
+ throw new Error("not found")
36
+ }),
37
+ },
38
+ _service: service,
39
+ }
40
+ }
41
+
42
+ function makeRes() {
43
+ return {
44
+ status: vi.fn().mockReturnThis(),
45
+ json: vi.fn().mockReturnThis(),
46
+ }
47
+ }
48
+
49
+ describe("GET /reconciliation", () => {
50
+ it("returns 400 when store_id is missing", async () => {
51
+ const req = makeReq({ date: "2026-06-23" })
52
+ const res = makeRes()
53
+ await GET(req as any, res as any)
54
+ expect(res.status).toHaveBeenCalledWith(400)
55
+ expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ code: "BAD_REQUEST" }))
56
+ })
57
+
58
+ it("returns 400 when date is missing", async () => {
59
+ const req = makeReq({ store_id: "s1" })
60
+ const res = makeRes()
61
+ await GET(req as any, res as any)
62
+ expect(res.status).toHaveBeenCalledWith(400)
63
+ expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ code: "BAD_REQUEST" }))
64
+ })
65
+
66
+ it("returns 400 when date format is invalid", async () => {
67
+ const req = makeReq({ store_id: "s1", date: "23-06-2026" })
68
+ const res = makeRes()
69
+ await GET(req as any, res as any)
70
+ expect(res.status).toHaveBeenCalledWith(400)
71
+ expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ code: "BAD_REQUEST" }))
72
+ })
73
+
74
+ it("returns reconciliation report on success", async () => {
75
+ const report = makeReport()
76
+ const service = makeService(report)
77
+ const req = makeReq({ store_id: "s1", date: "2026-06-23" }, service)
78
+ const res = makeRes()
79
+
80
+ await GET(req as any, res as any)
81
+
82
+ expect(res.status).toHaveBeenCalledWith(200)
83
+ expect(res.json).toHaveBeenCalledWith({ data: report })
84
+ })
85
+
86
+ it("passes net_cash_sales from query to service", async () => {
87
+ const service = makeService()
88
+ const req = makeReq({ store_id: "s1", date: "2026-06-23", net_cash_sales: "28450" }, service)
89
+ const res = makeRes()
90
+
91
+ await GET(req as any, res as any)
92
+
93
+ expect(service.getReconciliationReport).toHaveBeenCalledWith({
94
+ store_id: "s1",
95
+ date: "2026-06-23",
96
+ net_cash_sales: 28450,
97
+ })
98
+ })
99
+
100
+ it("defaults net_cash_sales to 0 when not provided", async () => {
101
+ const service = makeService()
102
+ const req = makeReq({ store_id: "s1", date: "2026-06-23" }, service)
103
+ const res = makeRes()
104
+
105
+ await GET(req as any, res as any)
106
+
107
+ expect(service.getReconciliationReport).toHaveBeenCalledWith({
108
+ store_id: "s1",
109
+ date: "2026-06-23",
110
+ net_cash_sales: 0,
111
+ })
112
+ })
113
+
114
+ it("returns 500 when service throws", async () => {
115
+ const service = { getReconciliationReport: vi.fn().mockRejectedValue(new Error("db error")) }
116
+ const req = makeReq({ store_id: "s1", date: "2026-06-23" }, service as any)
117
+ const res = makeRes()
118
+
119
+ await GET(req as any, res as any)
120
+
121
+ expect(res.status).toHaveBeenCalledWith(500)
122
+ expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ code: "INTERNAL_ERROR" }))
123
+ })
124
+ })
@@ -0,0 +1,47 @@
1
+ import type {
2
+ AuthenticatedMedusaRequest,
3
+ MedusaResponse,
4
+ } from "@medusajs/framework"
5
+ import { CMS_MODULE } from "../../../../../modules/cms/constants"
6
+ import type CmsModuleService from "../../../../../modules/cms/services/cms-module-service"
7
+
8
+ function getService(req: AuthenticatedMedusaRequest): CmsModuleService {
9
+ return (req as any).scope.resolve(CMS_MODULE)
10
+ }
11
+
12
+ function getLogger(req: AuthenticatedMedusaRequest) {
13
+ try {
14
+ return (req as any).scope.resolve("logger")
15
+ } catch {
16
+ return { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }
17
+ }
18
+ }
19
+
20
+ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/
21
+
22
+ export const GET = async (req: AuthenticatedMedusaRequest, res: MedusaResponse) => {
23
+ const query = req.query as Record<string, string>
24
+ const { store_id, date } = query
25
+
26
+ if (!store_id) {
27
+ return res.status(400).json({ code: "BAD_REQUEST", message: "store_id is required" })
28
+ }
29
+ if (!date || !DATE_RE.test(date)) {
30
+ return res.status(400).json({ code: "BAD_REQUEST", message: "date is required in YYYY-MM-DD format" })
31
+ }
32
+
33
+ const net_cash_sales = query.net_cash_sales !== undefined ? Number(query.net_cash_sales) : 0
34
+
35
+ try {
36
+ const service = getService(req)
37
+ const data = await service.getReconciliationReport({ store_id, date, net_cash_sales })
38
+ return res.status(200).json({ data })
39
+ } catch (err) {
40
+ getLogger(req).error("[retailos/cms] reconciliation report failed", {
41
+ store_id,
42
+ date,
43
+ error: err instanceof Error ? err.message : String(err),
44
+ })
45
+ return res.status(500).json({ code: "INTERNAL_ERROR", message: (err as Error).message })
46
+ }
47
+ }