@tailor-platform/erp-kit 0.0.1 → 0.1.1

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 (231) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +196 -28
  4. package/dist/cli.js +914 -0
  5. package/package.json +67 -8
  6. package/schemas/app-compose/actors.yml +34 -0
  7. package/schemas/app-compose/business-flow.yml +50 -0
  8. package/schemas/app-compose/requirements.yml +33 -0
  9. package/schemas/app-compose/resolver.yml +47 -0
  10. package/schemas/app-compose/screen.yml +81 -0
  11. package/schemas/app-compose/story.yml +67 -0
  12. package/schemas/module/command.yml +52 -0
  13. package/schemas/module/feature.yml +58 -0
  14. package/schemas/module/model.yml +70 -0
  15. package/schemas/module/module.yml +50 -0
  16. package/skills/1-module-docs/SKILL.md +111 -0
  17. package/skills/1-module-docs/references/structure.md +22 -0
  18. package/skills/2-module-feature-breakdown/SKILL.md +72 -0
  19. package/skills/2-module-feature-breakdown/references/commands.md +48 -0
  20. package/skills/2-module-feature-breakdown/references/models.md +29 -0
  21. package/skills/2-module-feature-breakdown/references/structure.md +22 -0
  22. package/skills/3-module-doc-review/SKILL.md +236 -0
  23. package/skills/3-module-doc-review/references/commands.md +54 -0
  24. package/skills/3-module-doc-review/references/models.md +29 -0
  25. package/skills/3-module-doc-review/references/testing.md +37 -0
  26. package/skills/4-module-tdd-implementation/SKILL.md +74 -0
  27. package/skills/4-module-tdd-implementation/references/commands.md +45 -0
  28. package/skills/4-module-tdd-implementation/references/db-relations.md +69 -0
  29. package/skills/4-module-tdd-implementation/references/errors.md +7 -0
  30. package/skills/4-module-tdd-implementation/references/exports.md +8 -0
  31. package/skills/4-module-tdd-implementation/references/models.md +30 -0
  32. package/skills/4-module-tdd-implementation/references/structure.md +22 -0
  33. package/skills/4-module-tdd-implementation/references/testing.md +37 -0
  34. package/skills/5-module-implementation-review/SKILL.md +408 -0
  35. package/skills/5-module-implementation-review/references/commands.md +45 -0
  36. package/skills/5-module-implementation-review/references/errors.md +7 -0
  37. package/skills/5-module-implementation-review/references/exports.md +8 -0
  38. package/skills/5-module-implementation-review/references/models.md +30 -0
  39. package/skills/5-module-implementation-review/references/testing.md +29 -0
  40. package/skills/app-compose-1-requirement-analysis/SKILL.md +89 -0
  41. package/skills/app-compose-1-requirement-analysis/references/structure.md +27 -0
  42. package/skills/app-compose-2-requirements-breakdown/SKILL.md +95 -0
  43. package/skills/app-compose-2-requirements-breakdown/references/screen-detailview.md +106 -0
  44. package/skills/app-compose-2-requirements-breakdown/references/screen-form.md +139 -0
  45. package/skills/app-compose-2-requirements-breakdown/references/screen-listview.md +153 -0
  46. package/skills/app-compose-2-requirements-breakdown/references/structure.md +27 -0
  47. package/skills/app-compose-3-doc-review/SKILL.md +116 -0
  48. package/skills/app-compose-3-doc-review/references/structure.md +27 -0
  49. package/skills/app-compose-4-design-mock/SKILL.md +256 -0
  50. package/skills/app-compose-4-design-mock/references/component.md +50 -0
  51. package/skills/app-compose-4-design-mock/references/screen-detailview.md +106 -0
  52. package/skills/app-compose-4-design-mock/references/screen-form.md +139 -0
  53. package/skills/app-compose-4-design-mock/references/screen-listview.md +153 -0
  54. package/skills/app-compose-4-design-mock/references/structure.md +27 -0
  55. package/skills/app-compose-5-design-mock-review/SKILL.md +290 -0
  56. package/skills/app-compose-5-design-mock-review/references/component.md +50 -0
  57. package/skills/app-compose-5-design-mock-review/references/screen-detailview.md +106 -0
  58. package/skills/app-compose-5-design-mock-review/references/screen-form.md +139 -0
  59. package/skills/app-compose-5-design-mock-review/references/screen-listview.md +153 -0
  60. package/skills/app-compose-6-implementation-spec/SKILL.md +127 -0
  61. package/skills/app-compose-6-implementation-spec/references/auth.md +72 -0
  62. package/skills/app-compose-6-implementation-spec/references/structure.md +27 -0
  63. package/skills/mock-scenario/SKILL.md +118 -0
  64. package/src/app.ts +1 -0
  65. package/src/cli.ts +120 -0
  66. package/src/commands/check.test.ts +30 -0
  67. package/src/commands/check.ts +66 -0
  68. package/src/commands/init.test.ts +88 -0
  69. package/src/commands/init.ts +120 -0
  70. package/src/commands/mock/index.ts +53 -0
  71. package/src/commands/mock/start.ts +179 -0
  72. package/src/commands/mock/validate.test.ts +185 -0
  73. package/src/commands/mock/validate.ts +198 -0
  74. package/src/commands/scaffold.test.ts +76 -0
  75. package/src/commands/scaffold.ts +119 -0
  76. package/src/commands/sync-check.test.ts +125 -0
  77. package/src/commands/sync-check.ts +182 -0
  78. package/src/integration.test.ts +63 -0
  79. package/src/mdschema.ts +48 -0
  80. package/src/mockServer.ts +55 -0
  81. package/src/module.ts +86 -0
  82. package/src/modules/accounting/.gitkeep +0 -0
  83. package/src/modules/coa-management/.gitkeep +0 -0
  84. package/src/modules/inventory/.gitkeep +0 -0
  85. package/src/modules/manufacturing/.gitkeep +0 -0
  86. package/src/modules/primitives/README.md +39 -0
  87. package/src/modules/primitives/command/activateCategory.test.ts +75 -0
  88. package/src/modules/primitives/command/activateCategory.ts +50 -0
  89. package/src/modules/primitives/command/activateCurrency.test.ts +70 -0
  90. package/src/modules/primitives/command/activateCurrency.ts +50 -0
  91. package/src/modules/primitives/command/activateUnit.test.ts +53 -0
  92. package/src/modules/primitives/command/activateUnit.ts +50 -0
  93. package/src/modules/primitives/command/convertAmount.test.ts +275 -0
  94. package/src/modules/primitives/command/convertAmount.ts +126 -0
  95. package/src/modules/primitives/command/convertQuantity.test.ts +219 -0
  96. package/src/modules/primitives/command/convertQuantity.ts +73 -0
  97. package/src/modules/primitives/command/createCategory.test.ts +126 -0
  98. package/src/modules/primitives/command/createCategory.ts +89 -0
  99. package/src/modules/primitives/command/createCurrency.test.ts +191 -0
  100. package/src/modules/primitives/command/createCurrency.ts +77 -0
  101. package/src/modules/primitives/command/createExchangeRate.test.ts +216 -0
  102. package/src/modules/primitives/command/createExchangeRate.ts +91 -0
  103. package/src/modules/primitives/command/createUnit.test.ts +214 -0
  104. package/src/modules/primitives/command/createUnit.ts +88 -0
  105. package/src/modules/primitives/command/deactivateCategory.test.ts +97 -0
  106. package/src/modules/primitives/command/deactivateCategory.ts +62 -0
  107. package/src/modules/primitives/command/deactivateCurrency.test.ts +85 -0
  108. package/src/modules/primitives/command/deactivateCurrency.ts +55 -0
  109. package/src/modules/primitives/command/deactivateUnit.test.ts +78 -0
  110. package/src/modules/primitives/command/deactivateUnit.ts +62 -0
  111. package/src/modules/primitives/command/setBaseCurrency.test.ts +98 -0
  112. package/src/modules/primitives/command/setBaseCurrency.ts +74 -0
  113. package/src/modules/primitives/command/setReferenceUnit.test.ts +108 -0
  114. package/src/modules/primitives/command/setReferenceUnit.ts +84 -0
  115. package/src/modules/primitives/db/currency.ts +30 -0
  116. package/src/modules/primitives/db/exchangeRate.ts +28 -0
  117. package/src/modules/primitives/db/unit.ts +32 -0
  118. package/src/modules/primitives/db/uomCategory.ts +32 -0
  119. package/src/modules/primitives/docs/commands/ActivateCategory.md +34 -0
  120. package/src/modules/primitives/docs/commands/ActivateCurrency.md +33 -0
  121. package/src/modules/primitives/docs/commands/ActivateUnit.md +34 -0
  122. package/src/modules/primitives/docs/commands/ConvertAmount.md +50 -0
  123. package/src/modules/primitives/docs/commands/ConvertQuantity.md +43 -0
  124. package/src/modules/primitives/docs/commands/CreateCategory.md +44 -0
  125. package/src/modules/primitives/docs/commands/CreateCurrency.md +47 -0
  126. package/src/modules/primitives/docs/commands/CreateExchangeRate.md +48 -0
  127. package/src/modules/primitives/docs/commands/CreateUnit.md +48 -0
  128. package/src/modules/primitives/docs/commands/DeactivateCategory.md +38 -0
  129. package/src/modules/primitives/docs/commands/DeactivateCurrency.md +38 -0
  130. package/src/modules/primitives/docs/commands/DeactivateUnit.md +38 -0
  131. package/src/modules/primitives/docs/commands/SetBaseCurrency.md +39 -0
  132. package/src/modules/primitives/docs/commands/SetReferenceUnit.md +43 -0
  133. package/src/modules/primitives/docs/features/currency-definitions.md +55 -0
  134. package/src/modules/primitives/docs/features/exchange-rates.md +61 -0
  135. package/src/modules/primitives/docs/features/unit-conversion.md +66 -0
  136. package/src/modules/primitives/docs/features/uom-categories.md +52 -0
  137. package/src/modules/primitives/docs/models/Currency.md +45 -0
  138. package/src/modules/primitives/docs/models/ExchangeRate.md +33 -0
  139. package/src/modules/primitives/docs/models/Unit.md +46 -0
  140. package/src/modules/primitives/docs/models/UoMCategory.md +44 -0
  141. package/src/modules/primitives/generated/kysely-tailordb.ts +95 -0
  142. package/src/modules/primitives/index.ts +40 -0
  143. package/src/modules/primitives/lib/errors.ts +138 -0
  144. package/src/modules/primitives/lib/types.ts +20 -0
  145. package/src/modules/primitives/module.ts +66 -0
  146. package/src/modules/primitives/permissions.ts +18 -0
  147. package/src/modules/primitives/tailor.config.ts +11 -0
  148. package/src/modules/primitives/testing/fixtures.ts +161 -0
  149. package/src/modules/product-management/.gitkeep +0 -0
  150. package/src/modules/purchase/.gitkeep +0 -0
  151. package/src/modules/sales/.gitkeep +0 -0
  152. package/src/modules/shared/createContext.test.ts +39 -0
  153. package/src/modules/shared/createContext.ts +15 -0
  154. package/src/modules/shared/defineCommand.test.ts +42 -0
  155. package/src/modules/shared/defineCommand.ts +19 -0
  156. package/src/modules/shared/definePermissions.test.ts +146 -0
  157. package/src/modules/shared/definePermissions.ts +94 -0
  158. package/src/modules/shared/entityTypes.ts +15 -0
  159. package/src/modules/shared/errors.ts +22 -0
  160. package/src/modules/shared/index.ts +1 -0
  161. package/src/modules/shared/internal.ts +13 -0
  162. package/src/modules/shared/requirePermission.test.ts +47 -0
  163. package/src/modules/shared/requirePermission.ts +8 -0
  164. package/src/modules/shared/types.ts +4 -0
  165. package/src/modules/supplier-management/.gitkeep +0 -0
  166. package/src/modules/supplier-portal/.gitkeep +0 -0
  167. package/src/modules/testing/index.ts +120 -0
  168. package/src/modules/user-management/README.md +38 -0
  169. package/src/modules/user-management/command/activateUser.test.ts +112 -0
  170. package/src/modules/user-management/command/activateUser.ts +67 -0
  171. package/src/modules/user-management/command/assignPermissionToRole.test.ts +119 -0
  172. package/src/modules/user-management/command/assignPermissionToRole.ts +87 -0
  173. package/src/modules/user-management/command/assignRoleToUser.test.ts +162 -0
  174. package/src/modules/user-management/command/assignRoleToUser.ts +93 -0
  175. package/src/modules/user-management/command/createPermission.test.ts +143 -0
  176. package/src/modules/user-management/command/createPermission.ts +66 -0
  177. package/src/modules/user-management/command/createRole.test.ts +115 -0
  178. package/src/modules/user-management/command/createRole.ts +52 -0
  179. package/src/modules/user-management/command/createUser.test.ts +198 -0
  180. package/src/modules/user-management/command/createUser.ts +85 -0
  181. package/src/modules/user-management/command/deactivateUser.test.ts +112 -0
  182. package/src/modules/user-management/command/deactivateUser.ts +67 -0
  183. package/src/modules/user-management/command/logAuditEvent.test.ts +179 -0
  184. package/src/modules/user-management/command/logAuditEvent.ts +59 -0
  185. package/src/modules/user-management/command/reactivateUser.test.ts +115 -0
  186. package/src/modules/user-management/command/reactivateUser.ts +67 -0
  187. package/src/modules/user-management/command/revokePermissionFromRole.test.ts +112 -0
  188. package/src/modules/user-management/command/revokePermissionFromRole.ts +81 -0
  189. package/src/modules/user-management/command/revokeRoleFromUser.test.ts +112 -0
  190. package/src/modules/user-management/command/revokeRoleFromUser.ts +81 -0
  191. package/src/modules/user-management/db/auditEvent.ts +47 -0
  192. package/src/modules/user-management/db/permission.ts +31 -0
  193. package/src/modules/user-management/db/role.ts +28 -0
  194. package/src/modules/user-management/db/rolePermission.ts +44 -0
  195. package/src/modules/user-management/db/user.ts +38 -0
  196. package/src/modules/user-management/db/userRole.ts +44 -0
  197. package/src/modules/user-management/docs/commands/ActivateUser.md +36 -0
  198. package/src/modules/user-management/docs/commands/AssignPermissionToRole.md +39 -0
  199. package/src/modules/user-management/docs/commands/AssignRoleToUser.md +43 -0
  200. package/src/modules/user-management/docs/commands/CreatePermission.md +35 -0
  201. package/src/modules/user-management/docs/commands/CreateRole.md +35 -0
  202. package/src/modules/user-management/docs/commands/CreateUser.md +41 -0
  203. package/src/modules/user-management/docs/commands/DeactivateUser.md +38 -0
  204. package/src/modules/user-management/docs/commands/LogAuditEvent.md +37 -0
  205. package/src/modules/user-management/docs/commands/ReactivateUser.md +37 -0
  206. package/src/modules/user-management/docs/commands/RevokePermissionFromRole.md +40 -0
  207. package/src/modules/user-management/docs/commands/RevokeRoleFromUser.md +40 -0
  208. package/src/modules/user-management/docs/features/audit-trail.md +80 -0
  209. package/src/modules/user-management/docs/features/role-based-access-control.md +76 -0
  210. package/src/modules/user-management/docs/features/user-account-management.md +64 -0
  211. package/src/modules/user-management/docs/models/AuditEvent.md +34 -0
  212. package/src/modules/user-management/docs/models/Permission.md +31 -0
  213. package/src/modules/user-management/docs/models/Role.md +31 -0
  214. package/src/modules/user-management/docs/models/RolePermission.md +33 -0
  215. package/src/modules/user-management/docs/models/User.md +47 -0
  216. package/src/modules/user-management/docs/models/UserRole.md +34 -0
  217. package/src/modules/user-management/docs/plans/2026-01-30-flattened-permissions-design.md +52 -0
  218. package/src/modules/user-management/executor/recomputeOnRolePermissionChange.ts +61 -0
  219. package/src/modules/user-management/generated/enums.ts +24 -0
  220. package/src/modules/user-management/generated/kysely-tailordb.ts +112 -0
  221. package/src/modules/user-management/index.ts +32 -0
  222. package/src/modules/user-management/lib/errors.ts +81 -0
  223. package/src/modules/user-management/lib/recomputeUserPermissions.ts +53 -0
  224. package/src/modules/user-management/lib/types.ts +31 -0
  225. package/src/modules/user-management/module.ts +77 -0
  226. package/src/modules/user-management/permissions.ts +15 -0
  227. package/src/modules/user-management/tailor.config.ts +11 -0
  228. package/src/modules/user-management/testing/fixtures.ts +98 -0
  229. package/src/schemas.ts +25 -0
  230. package/src/testing.ts +10 -0
  231. package/src/util.ts +3 -0
@@ -0,0 +1,63 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { fileURLToPath } from "node:url";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { describe, it, expect } from "vitest";
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const CLI_PATH = path.resolve(__dirname, "..", "dist", "cli.js");
9
+ const REPO_ROOT = path.resolve(__dirname, "..", "..", "..");
10
+
11
+ const skipReason = fs.existsSync(CLI_PATH) ? undefined : "Run `pnpm build` first";
12
+
13
+ describe.skipIf(skipReason)("integration", () => {
14
+ it("check command runs against sdk-plugins modules", () => {
15
+ // mdschema may report validation errors in real docs, so we allow non-zero exit codes
16
+ // but verify the CLI itself doesn't crash with an unexpected error
17
+ try {
18
+ const result = execFileSync(
19
+ "node",
20
+ [CLI_PATH, "check", "--modules-root", "packages/erp-kit/src/modules"],
21
+ {
22
+ cwd: REPO_ROOT,
23
+ encoding: "utf-8",
24
+ env: { ...process.env },
25
+ },
26
+ );
27
+ expect(result).toBeDefined();
28
+ } catch (err: unknown) {
29
+ const error = err as { status: number; stderr: string };
30
+ // exit code 1 = validation errors in docs (acceptable)
31
+ // exit code 2 = CLI usage error (unexpected)
32
+ expect(error.status).toBe(1);
33
+ }
34
+ });
35
+
36
+ it("sync-check command runs against sdk-plugins modules", () => {
37
+ try {
38
+ const result = execFileSync(
39
+ "node",
40
+ [CLI_PATH, "sync-check", "--modules-root", "packages/erp-kit/src/modules"],
41
+ {
42
+ cwd: REPO_ROOT,
43
+ encoding: "utf-8",
44
+ env: { ...process.env },
45
+ },
46
+ );
47
+ expect(result).toContain("docs-sync-check");
48
+ } catch (err: unknown) {
49
+ const error = err as { status: number; stdout: string };
50
+ // sync-check may fail with errors=1 but should still produce a report
51
+ expect(error.stdout).toContain("docs-sync-check");
52
+ }
53
+ });
54
+
55
+ it("--help prints usage", () => {
56
+ const result = execFileSync("node", [CLI_PATH, "--help"], {
57
+ encoding: "utf-8",
58
+ });
59
+ expect(result).toContain("erp-kit");
60
+ expect(result).toContain("check");
61
+ expect(result).toContain("scaffold");
62
+ });
63
+ });
@@ -0,0 +1,48 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import { execFile } from "node:child_process";
4
+ import { createRequire } from "node:module";
5
+
6
+ const require = createRequire(import.meta.url);
7
+
8
+ interface PackageJson {
9
+ bin?: string | Record<string, string>;
10
+ }
11
+
12
+ export function getMdschemaBin(): string {
13
+ const pkgPath = require.resolve("@jackchuka/mdschema/package.json");
14
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")) as PackageJson;
15
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.mdschema;
16
+ if (!bin) {
17
+ throw new Error("Could not resolve mdschema binary from package.json bin field");
18
+ }
19
+ return path.join(path.dirname(pkgPath), bin);
20
+ }
21
+
22
+ export interface MdschemaResult {
23
+ exitCode: number;
24
+ stdout: string;
25
+ stderr: string;
26
+ }
27
+
28
+ export function runMdschema(args: string[], cwd?: string): Promise<MdschemaResult> {
29
+ return new Promise((resolve) => {
30
+ execFile(
31
+ getMdschemaBin(),
32
+ args,
33
+ { encoding: "utf-8", cwd, timeout: 30_000 },
34
+ (error, stdout, stderr) => {
35
+ if (error) {
36
+ const execError = error as NodeJS.ErrnoException & { status?: number };
37
+ resolve({
38
+ exitCode: execError.code === "ENOENT" ? 127 : (execError.status ?? 1),
39
+ stdout: stdout ?? "",
40
+ stderr: stderr ?? "",
41
+ });
42
+ } else {
43
+ resolve({ exitCode: 0, stdout: stdout ?? "", stderr: stderr ?? "" });
44
+ }
45
+ },
46
+ );
47
+ });
48
+ }
@@ -0,0 +1,55 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+
4
+ export interface MockServer {
5
+ url: string;
6
+ port: number;
7
+ stop: () => Promise<void>;
8
+ }
9
+
10
+ /**
11
+ * Start a Mockoon mock server from a mock.json file.
12
+ * Returns a handle with the server URL and a stop function.
13
+ */
14
+ export async function createMockServer(mockJsonPath: string, port?: number): Promise<MockServer> {
15
+ const { MockoonServer, createLoggerInstance, listenServerEvents } =
16
+ await import("@mockoon/commons-server");
17
+ type Environment = import("@mockoon/commons").Environment;
18
+
19
+ const resolvedPath = resolve(mockJsonPath);
20
+ const environment = JSON.parse(readFileSync(resolvedPath, "utf-8")) as Environment;
21
+ if (port !== undefined) {
22
+ environment.port = port;
23
+ }
24
+ const actualPort = environment.port;
25
+
26
+ const logger = createLoggerInstance(null);
27
+ const server = new MockoonServer(environment, {
28
+ environmentDirectory: dirname(resolvedPath),
29
+ enableAdminApi: false,
30
+ disableTls: true,
31
+ enableRandomLatency: false,
32
+ maxTransactionLogs: 100,
33
+ envVarsPrefix: "MOCKOON_",
34
+ });
35
+
36
+ listenServerEvents(server, environment, logger, false);
37
+
38
+ await new Promise<void>((resolve, reject) => {
39
+ server.on("started", resolve);
40
+ server.on("error", (errorCode, originalError) => {
41
+ reject(originalError ?? new Error(`Mockoon error: ${errorCode}`));
42
+ });
43
+ server.start();
44
+ });
45
+
46
+ return {
47
+ url: `http://127.0.0.1:${actualPort}`,
48
+ port: actualPort,
49
+ stop: () =>
50
+ new Promise<void>((resolve) => {
51
+ server.on("stopped", resolve);
52
+ server.stop();
53
+ }),
54
+ };
55
+ }
package/src/module.ts ADDED
@@ -0,0 +1,86 @@
1
+ // shared/internal
2
+ export { defineCommand, type Command } from "./modules/shared/defineCommand";
3
+ export { definePermissions } from "./modules/shared/definePermissions";
4
+ export { requirePermission } from "./modules/shared/requirePermission";
5
+ export { createDomainError, InsufficientPermissionError } from "./modules/shared/errors";
6
+ export type { CommandContext } from "./modules/shared/types";
7
+ export type {
8
+ InferSchema,
9
+ Selectable,
10
+ Insertable,
11
+ Updateable,
12
+ FieldsToInsertable,
13
+ } from "./modules/shared/entityTypes";
14
+
15
+ // primitives
16
+ export { defineModule as definePrimitivesModule } from "./modules/primitives/module";
17
+ export {
18
+ permissions as primitivesPermissions,
19
+ own as primitivesOwn,
20
+ all as primitivesAll,
21
+ } from "./modules/primitives/permissions";
22
+ export {
23
+ UoMCategoryNotFoundError,
24
+ UnitNotFoundError,
25
+ IncompatibleUnitsError,
26
+ InactiveUnitError,
27
+ CurrencyNotFoundError,
28
+ InactiveCurrencyError,
29
+ ExchangeRateNotFoundError,
30
+ CannotDeactivateReferenceUnitError,
31
+ CategoryNotActiveError,
32
+ DuplicateUnitSymbolError,
33
+ InvalidConversionFactorError,
34
+ InvalidRoundingPrecisionError,
35
+ DuplicateCategoryNameError,
36
+ CategoryHasActiveUnitsError,
37
+ UnitNotInCategoryError,
38
+ InvalidISOCodeError,
39
+ DuplicateCurrencyCodeError,
40
+ InvalidDecimalPlacesError,
41
+ CannotDeactivateBaseCurrencyError,
42
+ CannotSetInactiveAsBaseCurrencyError,
43
+ SameCurrencyPairError,
44
+ InvalidExchangeRateError,
45
+ } from "./modules/primitives/lib/errors";
46
+ export { type ConvertQuantityInput } from "./modules/primitives/command/convertQuantity";
47
+ export { type ActivateCategoryInput } from "./modules/primitives/command/activateCategory";
48
+ export { type DeactivateCategoryInput } from "./modules/primitives/command/deactivateCategory";
49
+ export { type SetReferenceUnitInput } from "./modules/primitives/command/setReferenceUnit";
50
+ export { type ActivateUnitInput } from "./modules/primitives/command/activateUnit";
51
+ export { type DeactivateUnitInput } from "./modules/primitives/command/deactivateUnit";
52
+ export { type ConvertAmountInput } from "./modules/primitives/command/convertAmount";
53
+ export { type ActivateCurrencyInput } from "./modules/primitives/command/activateCurrency";
54
+ export { type DeactivateCurrencyInput } from "./modules/primitives/command/deactivateCurrency";
55
+ export { type SetBaseCurrencyInput } from "./modules/primitives/command/setBaseCurrency";
56
+ // user-management
57
+ export { defineModule as defineUserManagementModule } from "./modules/user-management/module";
58
+ export {
59
+ permissions as userManagementPermissions,
60
+ own as userManagementOwn,
61
+ all as userManagementAll,
62
+ } from "./modules/user-management/permissions";
63
+ export { AuditEventEventType, UserStatus } from "./modules/user-management/generated/enums";
64
+ export {
65
+ UserNotFoundError,
66
+ UserAlreadyExistsError,
67
+ UserNotActiveError,
68
+ InvalidEmailError,
69
+ MissingRequiredFieldError,
70
+ InvalidStatusTransitionError,
71
+ PermissionNotFoundError,
72
+ DuplicatePermissionKeyError,
73
+ InvalidPermissionKeyFormatError,
74
+ RoleNotFoundError,
75
+ DuplicateRoleNameError,
76
+ AssignmentNotFoundError,
77
+ InvalidEventTypeError,
78
+ } from "./modules/user-management/lib/errors";
79
+ export { type ActivateUserInput } from "./modules/user-management/command/activateUser";
80
+ export { type DeactivateUserInput } from "./modules/user-management/command/deactivateUser";
81
+ export { type ReactivateUserInput } from "./modules/user-management/command/reactivateUser";
82
+ export { type AssignPermissionToRoleInput } from "./modules/user-management/command/assignPermissionToRole";
83
+ export { type RevokePermissionFromRoleInput } from "./modules/user-management/command/revokePermissionFromRole";
84
+ export { type AssignRoleToUserInput } from "./modules/user-management/command/assignRoleToUser";
85
+ export { type RevokeRoleFromUserInput } from "./modules/user-management/command/revokeRoleFromUser";
86
+ export { type LogAuditEventInput } from "./modules/user-management/command/logAuditEvent";
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,39 @@
1
+ # README
2
+
3
+ ## Overview
4
+
5
+ The Primitives module provides foundational reference data that other ERP modules depend on. It includes unit of measure (UoM) definitions for quantity handling and currency definitions for multi-currency financial operations. These are stable, rarely-changing entities that form the measurement and monetary foundation of the system.
6
+
7
+ This module combines related configuration primitives to simplify dependency management while keeping tax-related functionality separate due to its regulatory complexity.
8
+
9
+ ## Key Features
10
+
11
+ - **UoM Categories**: Group related units (e.g., Unit, Weight, Volume, Length, Time) with a designated reference unit for each category
12
+ - **Unit Definitions**: Define individual units with symbols, names, and conversion factors relative to the reference unit
13
+ - **Quantity Conversion**: Convert quantities between any two units within the same category using automatic factor calculation
14
+ - **Rounding Precision**: Configure decimal precision per unit to ensure appropriate rounding for business operations
15
+ - **Currency Definitions**: Define currencies with ISO 4217 codes, symbols, and decimal precision
16
+ - **Base Currency**: Designate a company base currency for reporting and consolidation
17
+ - **Exchange Rates**: Maintain date-based exchange rates between currency pairs
18
+ - **Amount Conversion**: Convert monetary amounts between currencies using applicable rates
19
+
20
+ ## Module Scope
21
+
22
+ ### In Scope
23
+
24
+ - UoM category and unit management with conversion factors
25
+ - Currency definitions with ISO 4217 codes and symbols
26
+ - Exchange rate storage with effective dates
27
+ - Quantity conversion between compatible units
28
+ - Amount conversion between currencies
29
+
30
+ ### Out of Scope
31
+
32
+ - Tax configuration and fiscal positions (separate tax-configuration module)
33
+ - Product definitions (product-management module)
34
+ - Transaction recording (sales, purchase, accounting modules)
35
+ - Automatic exchange rate fetching from external APIs
36
+
37
+ ## Module Dependencies
38
+
39
+ - None (this is a foundational module)
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ createMockDb,
4
+ testNotFound,
5
+ testPermissionDenied,
6
+ testIdempotent,
7
+ } from "../../testing/index";
8
+ import { type CommandContext } from "../../shared/internal";
9
+ import { DB } from "../generated/kysely-tailordb";
10
+ import { UoMCategoryNotFoundError } from "../lib/errors";
11
+ import { baseUoMCategory } from "../testing/fixtures";
12
+ import { activateCategory } from "./activateCategory";
13
+
14
+ describe("activateCategory", () => {
15
+ const ctx: CommandContext = {
16
+ actorId: "test-actor",
17
+ permissions: ["primitives:activateCategory"],
18
+ };
19
+
20
+ const inactiveCategory = {
21
+ ...baseUoMCategory,
22
+ isActive: false,
23
+ };
24
+
25
+ it(
26
+ "throws when category doesn't exist",
27
+ testNotFound(
28
+ activateCategory,
29
+ { categoryId: "nonexistent-category" },
30
+ ctx,
31
+ UoMCategoryNotFoundError,
32
+ ),
33
+ );
34
+
35
+ it(
36
+ "returns category unchanged when already active",
37
+ testIdempotent(
38
+ activateCategory,
39
+ { categoryId: baseUoMCategory.id },
40
+ ctx,
41
+ baseUoMCategory,
42
+ "category",
43
+ ),
44
+ );
45
+
46
+ // Success cases
47
+ it("activates inactive category", async () => {
48
+ const { db, spies } = createMockDb<DB>();
49
+ const activatedCategory = {
50
+ ...inactiveCategory,
51
+ isActive: true,
52
+ updatedAt: new Date("2024-01-15T00:00:00.000Z"),
53
+ };
54
+
55
+ spies.select.mockReturnValue(inactiveCategory);
56
+ spies.update.mockReturnValue(activatedCategory);
57
+
58
+ const result = await activateCategory(
59
+ db,
60
+ {
61
+ categoryId: inactiveCategory.id,
62
+ },
63
+ ctx,
64
+ );
65
+
66
+ expect(result.category.isActive).toBe(true);
67
+ expect(result.category.updatedAt).not.toBeNull();
68
+ expect(spies.update).toHaveBeenCalled();
69
+ });
70
+
71
+ it(
72
+ "throws when permission is missing",
73
+ testPermissionDenied(activateCategory, { categoryId: "cat-1" }),
74
+ );
75
+ });
@@ -0,0 +1,50 @@
1
+ import { defineCommand } from "../../shared/internal";
2
+ import { DB } from "../generated/kysely-tailordb";
3
+ import { UoMCategoryNotFoundError } from "../lib/errors";
4
+ import { permissions } from "../permissions";
5
+
6
+ export interface ActivateCategoryInput {
7
+ categoryId: string;
8
+ }
9
+
10
+ /**
11
+ * Function: activateCategory
12
+ *
13
+ * Re-enables a previously deactivated UoM category, making it and its units
14
+ * available for new product assignments and transactions.
15
+ */
16
+ export const activateCategory = defineCommand(
17
+ permissions.activateCategory,
18
+ async (db: DB, input: ActivateCategoryInput) => {
19
+ // 1. Find category by ID
20
+ const category = await db
21
+ .selectFrom("UoMCategory")
22
+ .selectAll()
23
+ .where("id", "=", input.categoryId)
24
+ .executeTakeFirst();
25
+
26
+ // 2. If not found, throw error
27
+ if (!category) {
28
+ throw new UoMCategoryNotFoundError(input.categoryId);
29
+ }
30
+
31
+ // 3. If already active, return category (idempotent)
32
+ if (category.isActive) {
33
+ return { category };
34
+ }
35
+
36
+ // 4. Update isActive = true
37
+ const updatedCategory = await db
38
+ .updateTable("UoMCategory")
39
+ .set({
40
+ isActive: true,
41
+ updatedAt: new Date(),
42
+ })
43
+ .where("id", "=", input.categoryId)
44
+ .returningAll()
45
+ .executeTakeFirst();
46
+
47
+ // 5. Return updated category
48
+ return { category: updatedCategory! };
49
+ },
50
+ );
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ createMockDb,
4
+ testNotFound,
5
+ testPermissionDenied,
6
+ testIdempotent,
7
+ } from "../../testing/index";
8
+ import { type CommandContext } from "../../shared/internal";
9
+ import { DB } from "../generated/kysely-tailordb";
10
+ import { CurrencyNotFoundError } from "../lib/errors";
11
+ import { baseCurrencyUSD, inactiveCurrency } from "../testing/fixtures";
12
+ import { activateCurrency } from "./activateCurrency";
13
+
14
+ describe("activateCurrency", () => {
15
+ const ctx: CommandContext = {
16
+ actorId: "test-actor",
17
+ permissions: ["primitives:activateCurrency"],
18
+ };
19
+
20
+ it(
21
+ "throws when currency doesn't exist",
22
+ testNotFound(
23
+ activateCurrency,
24
+ { currencyId: "nonexistent-currency" },
25
+ ctx,
26
+ CurrencyNotFoundError,
27
+ ),
28
+ );
29
+
30
+ it(
31
+ "returns currency unchanged when already active",
32
+ testIdempotent(
33
+ activateCurrency,
34
+ { currencyId: baseCurrencyUSD.id },
35
+ ctx,
36
+ baseCurrencyUSD,
37
+ "currency",
38
+ ),
39
+ );
40
+
41
+ // Success cases
42
+ it("activates inactive currency", async () => {
43
+ const { db, spies } = createMockDb<DB>();
44
+ const activatedCurrency = {
45
+ ...inactiveCurrency,
46
+ isActive: true,
47
+ updatedAt: new Date("2024-01-15T00:00:00.000Z"),
48
+ };
49
+
50
+ spies.select.mockReturnValue(inactiveCurrency);
51
+ spies.update.mockReturnValue(activatedCurrency);
52
+
53
+ const result = await activateCurrency(
54
+ db,
55
+ {
56
+ currencyId: inactiveCurrency.id,
57
+ },
58
+ ctx,
59
+ );
60
+
61
+ expect(result.currency.isActive).toBe(true);
62
+ expect(result.currency.updatedAt).not.toBeNull();
63
+ expect(spies.update).toHaveBeenCalled();
64
+ });
65
+
66
+ it(
67
+ "throws when permission is missing",
68
+ testPermissionDenied(activateCurrency, { currencyId: "cur-1" }),
69
+ );
70
+ });
@@ -0,0 +1,50 @@
1
+ import { defineCommand } from "../../shared/internal";
2
+ import { DB } from "../generated/kysely-tailordb";
3
+ import { CurrencyNotFoundError } from "../lib/errors";
4
+ import { permissions } from "../permissions";
5
+
6
+ export interface ActivateCurrencyInput {
7
+ currencyId: string;
8
+ }
9
+
10
+ /**
11
+ * Function: activateCurrency
12
+ *
13
+ * Re-enables a previously deactivated currency, making it available
14
+ * for new transactions.
15
+ */
16
+ export const activateCurrency = defineCommand(
17
+ permissions.activateCurrency,
18
+ async (db: DB, input: ActivateCurrencyInput) => {
19
+ // 1. Find currency by ID
20
+ const currency = await db
21
+ .selectFrom("Currency")
22
+ .selectAll()
23
+ .where("id", "=", input.currencyId)
24
+ .executeTakeFirst();
25
+
26
+ // 2. If not found, throw error
27
+ if (!currency) {
28
+ throw new CurrencyNotFoundError(input.currencyId);
29
+ }
30
+
31
+ // 3. If already active, return currency (idempotent)
32
+ if (currency.isActive) {
33
+ return { currency };
34
+ }
35
+
36
+ // 4. Update isActive = true
37
+ const updatedCurrency = await db
38
+ .updateTable("Currency")
39
+ .set({
40
+ isActive: true,
41
+ updatedAt: new Date(),
42
+ })
43
+ .where("id", "=", input.currencyId)
44
+ .returningAll()
45
+ .executeTakeFirst();
46
+
47
+ // 5. Return updated currency
48
+ return { currency: updatedCurrency! };
49
+ },
50
+ );
@@ -0,0 +1,53 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ createMockDb,
4
+ testNotFound,
5
+ testPermissionDenied,
6
+ testIdempotent,
7
+ } from "../../testing/index";
8
+ import { type CommandContext } from "../../shared/internal";
9
+ import { DB } from "../generated/kysely-tailordb";
10
+ import { UnitNotFoundError } from "../lib/errors";
11
+ import { baseUnitKg, inactiveUnit } from "../testing/fixtures";
12
+ import { activateUnit } from "./activateUnit";
13
+
14
+ describe("activateUnit", () => {
15
+ const ctx: CommandContext = { actorId: "test-actor", permissions: ["primitives:activateUnit"] };
16
+
17
+ it(
18
+ "throws when unit doesn't exist",
19
+ testNotFound(activateUnit, { unitId: "nonexistent-unit" }, ctx, UnitNotFoundError),
20
+ );
21
+
22
+ it(
23
+ "returns unit unchanged when already active",
24
+ testIdempotent(activateUnit, { unitId: baseUnitKg.id }, ctx, baseUnitKg, "unit"),
25
+ );
26
+
27
+ // Success cases
28
+ it("activates inactive unit", async () => {
29
+ const { db, spies } = createMockDb<DB>();
30
+ const activatedUnit = {
31
+ ...inactiveUnit,
32
+ isActive: true,
33
+ updatedAt: new Date("2024-01-15T00:00:00.000Z"),
34
+ };
35
+
36
+ spies.select.mockReturnValue(inactiveUnit);
37
+ spies.update.mockReturnValue(activatedUnit);
38
+
39
+ const result = await activateUnit(
40
+ db,
41
+ {
42
+ unitId: inactiveUnit.id,
43
+ },
44
+ ctx,
45
+ );
46
+
47
+ expect(result.unit.isActive).toBe(true);
48
+ expect(result.unit.updatedAt).not.toBeNull();
49
+ expect(spies.update).toHaveBeenCalled();
50
+ });
51
+
52
+ it("throws when permission is missing", testPermissionDenied(activateUnit, { unitId: "unit-1" }));
53
+ });
@@ -0,0 +1,50 @@
1
+ import { defineCommand } from "../../shared/internal";
2
+ import { DB } from "../generated/kysely-tailordb";
3
+ import { UnitNotFoundError } from "../lib/errors";
4
+ import { permissions } from "../permissions";
5
+
6
+ export interface ActivateUnitInput {
7
+ unitId: string;
8
+ }
9
+
10
+ /**
11
+ * Function: activateUnit
12
+ *
13
+ * Re-enables a previously deactivated unit of measure, making it available
14
+ * for new product assignments and quantity conversions.
15
+ */
16
+ export const activateUnit = defineCommand(
17
+ permissions.activateUnit,
18
+ async (db: DB, input: ActivateUnitInput) => {
19
+ // 1. Find unit by ID
20
+ const unit = await db
21
+ .selectFrom("Unit")
22
+ .selectAll()
23
+ .where("id", "=", input.unitId)
24
+ .executeTakeFirst();
25
+
26
+ // 2. If not found, throw error
27
+ if (!unit) {
28
+ throw new UnitNotFoundError(input.unitId);
29
+ }
30
+
31
+ // 3. If already active, return unit (idempotent)
32
+ if (unit.isActive) {
33
+ return { unit };
34
+ }
35
+
36
+ // 4. Update isActive = true
37
+ const updatedUnit = await db
38
+ .updateTable("Unit")
39
+ .set({
40
+ isActive: true,
41
+ updatedAt: new Date(),
42
+ })
43
+ .where("id", "=", input.unitId)
44
+ .returningAll()
45
+ .executeTakeFirst();
46
+
47
+ // 5. Return updated unit
48
+ return { unit: updatedUnit! };
49
+ },
50
+ );