@tailor-platform/erp-kit 0.1.2 → 0.2.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.
- package/CHANGELOG.md +12 -0
- package/README.md +80 -12
- package/dist/cli.js +1070 -450
- package/package.json +11 -8
- package/schemas/app-compose/business-flow.yml +3 -0
- package/schemas/app-compose/story.yml +1 -1
- package/schemas/module/model.yml +5 -0
- package/skills/{app-compose-1-requirement-analysis → erp-kit-app-1-requirements}/SKILL.md +8 -14
- package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/SKILL.md +6 -13
- package/skills/{app-compose-3-doc-review → erp-kit-app-3-doc-review}/SKILL.md +2 -6
- package/skills/{app-compose-6-implementation-spec → erp-kit-app-4-impl-spec}/SKILL.md +11 -22
- package/skills/erp-kit-app-5-implementation/SKILL.md +149 -0
- package/skills/erp-kit-app-5-implementation/references/backend.md +232 -0
- package/skills/erp-kit-app-5-implementation/references/frontend.md +242 -0
- package/skills/{mock-scenario → erp-kit-mock-scenario}/SKILL.md +1 -1
- package/skills/{1-module-docs → erp-kit-module-1-docs}/SKILL.md +2 -2
- package/skills/{2-module-feature-breakdown → erp-kit-module-2-feature-breakdown}/SKILL.md +13 -9
- package/skills/erp-kit-module-2-feature-breakdown/references/naming.md +59 -0
- package/skills/{3-module-doc-review → erp-kit-module-3-doc-review}/SKILL.md +83 -25
- package/skills/erp-kit-module-4-tdd/SKILL.md +94 -0
- package/skills/erp-kit-module-4-tdd/references/cross-module-dependency.md +133 -0
- package/skills/{4-module-tdd-implementation → erp-kit-module-4-tdd}/references/db-relations.md +5 -1
- package/skills/{4-module-tdd-implementation → erp-kit-module-4-tdd}/references/exports.md +1 -1
- package/skills/erp-kit-module-4-tdd/references/generated-code.md +32 -0
- package/skills/{5-module-implementation-review → erp-kit-module-5-impl-review}/SKILL.md +46 -44
- package/skills/erp-kit-module-5-impl-review/references/commands.md +62 -0
- package/skills/erp-kit-module-5-impl-review/references/errors.md +10 -0
- package/skills/{5-module-implementation-review → erp-kit-module-5-impl-review}/references/testing.md +1 -1
- package/skills/erp-kit-module-shared/SKILL.md +16 -0
- package/skills/erp-kit-module-shared/references/commands.md +203 -0
- package/skills/erp-kit-module-shared/references/errors.md +35 -0
- package/skills/erp-kit-module-shared/references/queries.md +168 -0
- package/skills/erp-kit-module-shared/references/structure.md +36 -0
- package/skills/{3-module-doc-review → erp-kit-module-shared}/references/testing.md +4 -3
- package/skills/erp-kit-update/SKILL.md +64 -0
- package/src/cli.doc.test.ts +65 -0
- package/src/cli.ts +3 -35
- package/src/commands/app/index.ts +3 -3
- package/src/commands/check.test.ts +1 -1
- package/src/commands/check.ts +2 -2
- package/src/commands/index.ts +73 -0
- package/src/commands/init.test.ts +22 -5
- package/src/commands/init.ts +25 -16
- package/src/commands/license.ts +193 -0
- package/src/commands/mock/index.ts +2 -2
- package/src/commands/mock/start.ts +1 -1
- package/src/commands/mock/validate.test.ts +1 -1
- package/src/commands/module/generate.ts +35 -0
- package/src/commands/module/index.ts +6 -4
- package/src/commands/module/list.test.ts +7 -12
- package/src/commands/module/list.ts +1 -1
- package/src/commands/scaffold-templates.ts +65 -0
- package/src/commands/scaffold.test.ts +92 -2
- package/src/commands/scaffold.ts +22 -2
- package/src/commands/sync-check.test.ts +60 -1
- package/src/commands/sync-check.ts +35 -2
- package/src/generator/generate-code.test.ts +200 -0
- package/src/generator/generate-code.ts +260 -0
- package/src/generator/parse-command-doc.test.ts +159 -0
- package/src/generator/parse-command-doc.ts +116 -0
- package/src/integration.test.ts +2 -2
- package/src/module.ts +44 -6
- package/src/modules/item-management/README.md +38 -0
- package/src/modules/item-management/command/activateItem.generated.ts +6 -0
- package/src/modules/item-management/command/activateItem.test.ts +76 -0
- package/src/modules/item-management/command/activateItem.ts +42 -0
- package/src/modules/item-management/command/assignItemToTaxonomy.generated.ts +6 -0
- package/src/modules/item-management/command/assignItemToTaxonomy.test.ts +88 -0
- package/src/modules/item-management/command/assignItemToTaxonomy.ts +63 -0
- package/src/modules/item-management/command/createItem.generated.ts +6 -0
- package/src/modules/item-management/command/createItem.test.ts +152 -0
- package/src/modules/item-management/command/createItem.ts +72 -0
- package/src/modules/item-management/command/createTaxonomyNode.generated.ts +6 -0
- package/src/modules/item-management/command/createTaxonomyNode.test.ts +126 -0
- package/src/modules/item-management/command/createTaxonomyNode.ts +70 -0
- package/src/modules/item-management/command/deactivateItem.generated.ts +6 -0
- package/src/modules/item-management/command/deactivateItem.test.ts +76 -0
- package/src/modules/item-management/command/deactivateItem.ts +42 -0
- package/src/modules/item-management/command/deleteItem.generated.ts +6 -0
- package/src/modules/item-management/command/deleteItem.test.ts +61 -0
- package/src/modules/item-management/command/deleteItem.ts +38 -0
- package/src/modules/item-management/command/deleteTaxonomyNode.generated.ts +6 -0
- package/src/modules/item-management/command/deleteTaxonomyNode.test.ts +73 -0
- package/src/modules/item-management/command/deleteTaxonomyNode.ts +50 -0
- package/src/modules/item-management/command/moveTaxonomyNode.generated.ts +6 -0
- package/src/modules/item-management/command/moveTaxonomyNode.test.ts +136 -0
- package/src/modules/item-management/command/moveTaxonomyNode.ts +85 -0
- package/src/modules/item-management/command/reactivateItem.generated.ts +6 -0
- package/src/modules/item-management/command/reactivateItem.test.ts +76 -0
- package/src/modules/item-management/command/reactivateItem.ts +42 -0
- package/src/modules/item-management/command/removeItemFromTaxonomy.generated.ts +6 -0
- package/src/modules/item-management/command/removeItemFromTaxonomy.test.ts +43 -0
- package/src/modules/item-management/command/removeItemFromTaxonomy.ts +30 -0
- package/src/modules/item-management/command/updateItem.generated.ts +6 -0
- package/src/modules/item-management/command/updateItem.test.ts +178 -0
- package/src/modules/item-management/command/updateItem.ts +103 -0
- package/src/modules/item-management/command/updateTaxonomyNode.generated.ts +6 -0
- package/src/modules/item-management/command/updateTaxonomyNode.test.ts +88 -0
- package/src/modules/item-management/command/updateTaxonomyNode.ts +62 -0
- package/src/modules/item-management/db/item.ts +47 -0
- package/src/modules/item-management/db/itemTaxonomyAssignment.ts +49 -0
- package/src/modules/item-management/db/taxonomyNode.ts +34 -0
- package/src/modules/item-management/docs/commands/ActivateItem.md +32 -0
- package/src/modules/item-management/docs/commands/AssignItemToTaxonomy.md +38 -0
- package/src/modules/item-management/docs/commands/CreateItem.md +44 -0
- package/src/modules/item-management/docs/commands/CreateTaxonomyNode.md +44 -0
- package/src/modules/item-management/docs/commands/DeactivateItem.md +34 -0
- package/src/modules/item-management/docs/commands/DeleteItem.md +35 -0
- package/src/modules/item-management/docs/commands/DeleteTaxonomyNode.md +39 -0
- package/src/modules/item-management/docs/commands/MoveTaxonomyNode.md +45 -0
- package/src/modules/item-management/docs/commands/ReactivateItem.md +34 -0
- package/src/modules/item-management/docs/commands/RemoveItemFromTaxonomy.md +30 -0
- package/src/modules/item-management/docs/commands/UpdateItem.md +55 -0
- package/src/modules/item-management/docs/commands/UpdateTaxonomyNode.md +36 -0
- package/src/modules/item-management/docs/features/item-lifecycle.md +60 -0
- package/src/modules/item-management/docs/features/item-taxonomy.md +65 -0
- package/src/modules/item-management/docs/models/ItemTaxonomyAssignment.md +36 -0
- package/src/modules/item-management/docs/models/TaxonomyNode.md +47 -0
- package/src/modules/item-management/docs/models/item.md +59 -0
- package/src/modules/item-management/docs/queries/CalculateNodeDepth.md +36 -0
- package/src/modules/item-management/docs/queries/CalculateSubtreeDepth.md +40 -0
- package/src/modules/item-management/docs/queries/DetectCircularReference.md +41 -0
- package/src/modules/item-management/docs/queries/GetItem.md +38 -0
- package/src/modules/item-management/docs/queries/GetItemTaxonomyAssignment.md +29 -0
- package/src/modules/item-management/docs/queries/GetTaxonomyNode.md +35 -0
- package/src/modules/item-management/docs/queries/GetTaxonomyNodeAssignments.md +29 -0
- package/src/modules/item-management/docs/queries/GetTaxonomyNodeChildren.md +29 -0
- package/src/modules/item-management/generated/enums.ts +9 -0
- package/src/modules/item-management/generated/kysely-tailordb.ts +62 -0
- package/src/modules/item-management/index.ts +53 -0
- package/src/modules/item-management/lib/_db_deps.ts +13 -0
- package/src/modules/item-management/lib/errors.generated.ts +117 -0
- package/src/modules/item-management/lib/permissions.generated.ts +17 -0
- package/src/modules/item-management/lib/types.ts +19 -0
- package/src/modules/item-management/module.ts +97 -0
- package/src/modules/item-management/query/calculateNodeDepth.generated.ts +5 -0
- package/src/modules/item-management/query/calculateNodeDepth.test.ts +56 -0
- package/src/modules/item-management/query/calculateNodeDepth.ts +28 -0
- package/src/modules/item-management/query/calculateSubtreeDepth.generated.ts +5 -0
- package/src/modules/item-management/query/calculateSubtreeDepth.test.ts +75 -0
- package/src/modules/item-management/query/calculateSubtreeDepth.ts +29 -0
- package/src/modules/item-management/query/detectCircularReference.generated.ts +5 -0
- package/src/modules/item-management/query/detectCircularReference.test.ts +61 -0
- package/src/modules/item-management/query/detectCircularReference.ts +32 -0
- package/src/modules/item-management/query/getItem.generated.ts +5 -0
- package/src/modules/item-management/query/getItem.test.ts +67 -0
- package/src/modules/item-management/query/getItem.ts +20 -0
- package/src/modules/item-management/query/getItemTaxonomyAssignment.generated.ts +5 -0
- package/src/modules/item-management/query/getItemTaxonomyAssignment.test.ts +25 -0
- package/src/modules/item-management/query/getItemTaxonomyAssignment.ts +18 -0
- package/src/modules/item-management/query/getTaxonomyNode.generated.ts +5 -0
- package/src/modules/item-management/query/getTaxonomyNode.test.ts +47 -0
- package/src/modules/item-management/query/getTaxonomyNode.ts +18 -0
- package/src/modules/item-management/query/getTaxonomyNodeAssignments.generated.ts +5 -0
- package/src/modules/item-management/query/getTaxonomyNodeAssignments.test.ts +25 -0
- package/src/modules/item-management/query/getTaxonomyNodeAssignments.ts +16 -0
- package/src/modules/item-management/query/getTaxonomyNodeChildren.generated.ts +5 -0
- package/src/modules/item-management/query/getTaxonomyNodeChildren.test.ts +34 -0
- package/src/modules/item-management/query/getTaxonomyNodeChildren.ts +16 -0
- package/src/modules/item-management/tailor.config.ts +11 -0
- package/src/modules/item-management/testing/fixtures.ts +81 -0
- package/src/modules/primitives/command/activateCategory.generated.ts +6 -0
- package/src/modules/primitives/command/activateCategory.test.ts +11 -29
- package/src/modules/primitives/command/activateCategory.ts +27 -34
- package/src/modules/primitives/command/activateCurrency.generated.ts +6 -0
- package/src/modules/primitives/command/activateCurrency.test.ts +11 -29
- package/src/modules/primitives/command/activateCurrency.ts +27 -34
- package/src/modules/primitives/command/activateUnit.generated.ts +6 -0
- package/src/modules/primitives/command/activateUnit.test.ts +11 -15
- package/src/modules/primitives/command/activateUnit.ts +27 -34
- package/src/modules/primitives/command/createCategory.generated.ts +6 -0
- package/src/modules/primitives/command/createCategory.test.ts +27 -39
- package/src/modules/primitives/command/createCategory.ts +53 -62
- package/src/modules/primitives/command/createCurrency.generated.ts +6 -0
- package/src/modules/primitives/command/createCurrency.test.ts +78 -71
- package/src/modules/primitives/command/createCurrency.ts +43 -48
- package/src/modules/primitives/command/createExchangeRate.generated.ts +6 -0
- package/src/modules/primitives/command/createExchangeRate.test.ts +101 -100
- package/src/modules/primitives/command/createExchangeRate.ts +50 -59
- package/src/modules/primitives/command/createUnit.generated.ts +6 -0
- package/src/modules/primitives/command/createUnit.test.ts +92 -95
- package/src/modules/primitives/command/createUnit.ts +54 -57
- package/src/modules/primitives/command/deactivateCategory.generated.ts +6 -0
- package/src/modules/primitives/command/deactivateCategory.test.ts +27 -28
- package/src/modules/primitives/command/deactivateCategory.ts +43 -50
- package/src/modules/primitives/command/deactivateCurrency.generated.ts +6 -0
- package/src/modules/primitives/command/deactivateCurrency.test.ts +23 -38
- package/src/modules/primitives/command/deactivateCurrency.ts +31 -38
- package/src/modules/primitives/command/deactivateUnit.generated.ts +6 -0
- package/src/modules/primitives/command/deactivateUnit.test.ts +27 -23
- package/src/modules/primitives/command/deactivateUnit.ts +39 -49
- package/src/modules/primitives/command/setBaseCurrency.generated.ts +6 -0
- package/src/modules/primitives/command/setBaseCurrency.test.ts +40 -33
- package/src/modules/primitives/command/setBaseCurrency.ts +43 -50
- package/src/modules/primitives/command/setReferenceUnit.generated.ts +6 -0
- package/src/modules/primitives/command/setReferenceUnit.test.ts +39 -35
- package/src/modules/primitives/command/setReferenceUnit.ts +46 -59
- package/src/modules/primitives/db/unit.ts +13 -3
- package/src/modules/primitives/docs/commands/ActivateCategory.md +1 -2
- package/src/modules/primitives/docs/commands/ActivateCurrency.md +1 -2
- package/src/modules/primitives/docs/commands/ActivateUnit.md +1 -2
- package/src/modules/primitives/docs/commands/CreateCategory.md +1 -4
- package/src/modules/primitives/docs/commands/CreateCurrency.md +3 -4
- package/src/modules/primitives/docs/commands/CreateExchangeRate.md +4 -5
- package/src/modules/primitives/docs/commands/CreateUnit.md +5 -5
- package/src/modules/primitives/docs/commands/DeactivateCategory.md +2 -3
- package/src/modules/primitives/docs/commands/DeactivateCurrency.md +2 -3
- package/src/modules/primitives/docs/commands/DeactivateUnit.md +2 -3
- package/src/modules/primitives/docs/commands/SetBaseCurrency.md +2 -3
- package/src/modules/primitives/docs/commands/SetReferenceUnit.md +2 -3
- package/src/modules/primitives/docs/queries/ConvertAmount.md +3 -5
- package/src/modules/primitives/docs/queries/ConvertQuantity.md +3 -5
- package/src/modules/primitives/docs/queries/GetBaseCurrency.md +32 -0
- package/src/modules/primitives/docs/queries/GetCurrency.md +36 -0
- package/src/modules/primitives/docs/queries/GetUnit.md +36 -0
- package/src/modules/primitives/docs/queries/GetUoMCategory.md +36 -0
- package/src/modules/primitives/docs/queries/ListUnitsByCategory.md +26 -0
- package/src/modules/primitives/generated/kysely-tailordb.ts +24 -45
- package/src/modules/primitives/index.ts +15 -4
- package/src/modules/primitives/lib/errors.generated.ts +112 -0
- package/src/modules/primitives/{permissions.ts → lib/permissions.generated.ts} +9 -8
- package/src/modules/primitives/module.ts +37 -27
- package/src/modules/primitives/query/convertAmount.generated.ts +5 -0
- package/src/modules/primitives/query/convertAmount.test.ts +2 -2
- package/src/modules/primitives/query/convertAmount.ts +27 -28
- package/src/modules/primitives/query/convertQuantity.generated.ts +5 -0
- package/src/modules/primitives/query/convertQuantity.test.ts +6 -2
- package/src/modules/primitives/query/convertQuantity.ts +49 -57
- package/src/modules/primitives/query/getBaseCurrency.generated.ts +5 -0
- package/src/modules/primitives/query/getBaseCurrency.test.ts +28 -0
- package/src/modules/primitives/query/getBaseCurrency.ts +16 -0
- package/src/modules/primitives/query/getCurrency.generated.ts +5 -0
- package/src/modules/primitives/query/getCurrency.test.ts +47 -0
- package/src/modules/primitives/query/getCurrency.ts +18 -0
- package/src/modules/primitives/query/getUnit.generated.ts +5 -0
- package/src/modules/primitives/query/getUnit.test.ts +47 -0
- package/src/modules/primitives/query/getUnit.ts +18 -0
- package/src/modules/primitives/query/getUoMCategory.generated.ts +5 -0
- package/src/modules/primitives/query/getUoMCategory.test.ts +47 -0
- package/src/modules/primitives/query/getUoMCategory.ts +18 -0
- package/src/modules/primitives/query/listUnitsByCategory.generated.ts +5 -0
- package/src/modules/primitives/query/listUnitsByCategory.ts +16 -0
- package/src/modules/primitives/tailor.config.ts +3 -3
- package/src/modules/shared/defineCommand.test.ts +23 -10
- package/src/modules/shared/defineCommand.ts +23 -10
- package/src/modules/shared/internal.ts +1 -0
- package/src/modules/shared/requirePermission.test.ts +22 -21
- package/src/modules/shared/requirePermission.ts +8 -2
- package/src/modules/shared/result.ts +12 -0
- package/src/modules/testing/index.ts +36 -11
- package/src/modules/user-management/command/activateUser.generated.ts +6 -0
- package/src/modules/user-management/command/activateUser.test.ts +27 -27
- package/src/modules/user-management/command/activateUser.ts +40 -48
- package/src/modules/user-management/command/assignPermissionToRole.generated.ts +6 -0
- package/src/modules/user-management/command/assignPermissionToRole.test.ts +42 -43
- package/src/modules/user-management/command/assignPermissionToRole.ts +59 -62
- package/src/modules/user-management/command/assignRoleToUser.generated.ts +6 -0
- package/src/modules/user-management/command/assignRoleToUser.test.ts +70 -63
- package/src/modules/user-management/command/assignRoleToUser.ts +63 -66
- package/src/modules/user-management/command/createPermission.generated.ts +6 -0
- package/src/modules/user-management/command/createPermission.test.ts +45 -38
- package/src/modules/user-management/command/createPermission.ts +42 -46
- package/src/modules/user-management/command/createRole.generated.ts +6 -0
- package/src/modules/user-management/command/createRole.test.ts +30 -29
- package/src/modules/user-management/command/createRole.ts +33 -33
- package/src/modules/user-management/command/createUser.generated.ts +6 -0
- package/src/modules/user-management/command/createUser.test.ts +64 -42
- package/src/modules/user-management/command/createUser.ts +54 -56
- package/src/modules/user-management/command/deactivateUser.generated.ts +6 -0
- package/src/modules/user-management/command/deactivateUser.test.ts +27 -27
- package/src/modules/user-management/command/deactivateUser.ts +40 -48
- package/src/modules/user-management/command/logAuditEvent.generated.ts +6 -0
- package/src/modules/user-management/command/logAuditEvent.test.ts +50 -42
- package/src/modules/user-management/command/logAuditEvent.ts +25 -28
- package/src/modules/user-management/command/reactivateUser.generated.ts +6 -0
- package/src/modules/user-management/command/reactivateUser.test.ts +31 -27
- package/src/modules/user-management/command/reactivateUser.ts +40 -48
- package/src/modules/user-management/command/revokePermissionFromRole.generated.ts +6 -0
- package/src/modules/user-management/command/revokePermissionFromRole.test.ts +52 -51
- package/src/modules/user-management/command/revokePermissionFromRole.ts +60 -57
- package/src/modules/user-management/command/revokeRoleFromUser.generated.ts +6 -0
- package/src/modules/user-management/command/revokeRoleFromUser.test.ts +53 -48
- package/src/modules/user-management/command/revokeRoleFromUser.ts +58 -57
- package/src/modules/user-management/docs/commands/CreatePermission.md +2 -2
- package/src/modules/user-management/docs/commands/CreateRole.md +1 -1
- package/src/modules/user-management/generated/enums.ts +11 -11
- package/src/modules/user-management/generated/kysely-tailordb.ts +27 -56
- package/src/modules/user-management/index.ts +2 -2
- package/src/modules/user-management/lib/errors.generated.ts +67 -0
- package/src/modules/user-management/{permissions.ts → lib/permissions.generated.ts} +8 -7
- package/src/modules/user-management/module.ts +22 -22
- package/src/modules/user-management/tailor.config.ts +3 -3
- package/src/schemas.ts +1 -1
- package/skills/1-module-docs/references/structure.md +0 -22
- package/skills/2-module-feature-breakdown/references/commands.md +0 -48
- package/skills/2-module-feature-breakdown/references/structure.md +0 -22
- package/skills/3-module-doc-review/references/commands.md +0 -54
- package/skills/3-module-doc-review/references/models.md +0 -29
- package/skills/4-module-tdd-implementation/SKILL.md +0 -74
- package/skills/4-module-tdd-implementation/references/commands.md +0 -45
- package/skills/4-module-tdd-implementation/references/errors.md +0 -7
- package/skills/4-module-tdd-implementation/references/models.md +0 -30
- package/skills/4-module-tdd-implementation/references/structure.md +0 -22
- package/skills/4-module-tdd-implementation/references/testing.md +0 -37
- package/skills/5-module-implementation-review/references/commands.md +0 -45
- package/skills/5-module-implementation-review/references/errors.md +0 -7
- package/skills/5-module-implementation-review/references/exports.md +0 -8
- package/skills/5-module-implementation-review/references/models.md +0 -30
- package/skills/app-compose-1-requirement-analysis/references/structure.md +0 -27
- package/skills/app-compose-2-requirements-breakdown/references/screen-detailview.md +0 -106
- package/skills/app-compose-2-requirements-breakdown/references/screen-form.md +0 -139
- package/skills/app-compose-2-requirements-breakdown/references/screen-listview.md +0 -153
- package/skills/app-compose-2-requirements-breakdown/references/structure.md +0 -27
- package/skills/app-compose-3-doc-review/references/structure.md +0 -27
- package/skills/app-compose-4-design-mock/SKILL.md +0 -256
- package/skills/app-compose-4-design-mock/references/component.md +0 -50
- package/skills/app-compose-4-design-mock/references/screen-detailview.md +0 -106
- package/skills/app-compose-4-design-mock/references/screen-form.md +0 -139
- package/skills/app-compose-4-design-mock/references/screen-listview.md +0 -153
- package/skills/app-compose-4-design-mock/references/structure.md +0 -27
- package/skills/app-compose-5-design-mock-review/SKILL.md +0 -290
- package/skills/app-compose-5-design-mock-review/references/component.md +0 -50
- package/skills/app-compose-5-design-mock-review/references/screen-detailview.md +0 -106
- package/skills/app-compose-5-design-mock-review/references/screen-form.md +0 -139
- package/skills/app-compose-5-design-mock-review/references/screen-listview.md +0 -153
- package/skills/app-compose-6-implementation-spec/references/auth.md +0 -72
- package/skills/app-compose-6-implementation-spec/references/structure.md +0 -27
- package/src/modules/primitives/lib/errors.ts +0 -138
- package/src/modules/user-management/lib/errors.ts +0 -81
- /package/skills/{2-module-feature-breakdown → erp-kit-module-4-tdd}/references/models.md +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { createDomainError } from "../../shared/internal";
|
|
3
|
+
|
|
4
|
+
export const ItemNotFoundError = createDomainError(
|
|
5
|
+
"ItemNotFoundError", "ITEM_NOT_FOUND",
|
|
6
|
+
(identifier: string) => `Specified item ID does not exist: ${identifier}`,
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
export const InvalidStateTransitionError = createDomainError(
|
|
10
|
+
"InvalidStateTransitionError", "INVALID_STATE_TRANSITION",
|
|
11
|
+
(identifier: string) => `Item is not in DRAFT status: ${identifier}`,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export const NodeNotFoundError = createDomainError(
|
|
15
|
+
"NodeNotFoundError", "NODE_NOT_FOUND",
|
|
16
|
+
(identifier: string) => `Specified taxonomy node ID does not exist: ${identifier}`,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const DuplicateAssignmentError = createDomainError(
|
|
20
|
+
"DuplicateAssignmentError", "DUPLICATE_ASSIGNMENT",
|
|
21
|
+
(identifier: string) => `Item is already assigned to this taxonomy node: ${identifier}`,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export const DuplicateSkuError = createDomainError(
|
|
25
|
+
"DuplicateSkuError", "DUPLICATE_SKU",
|
|
26
|
+
(identifier: string) => `An item with the same SKU already exists: ${identifier}`,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export const DuplicateBarcodeError = createDomainError(
|
|
30
|
+
"DuplicateBarcodeError", "DUPLICATE_BARCODE",
|
|
31
|
+
(identifier: string) => `An item with the same barcode already exists: ${identifier}`,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export const UnitNotFoundError = createDomainError(
|
|
35
|
+
"UnitNotFoundError", "UNIT_NOT_FOUND",
|
|
36
|
+
(identifier: string) => `Referenced unit does not exist or is inactive: ${identifier}`,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export const DuplicateNodeCodeError = createDomainError(
|
|
40
|
+
"DuplicateNodeCodeError", "DUPLICATE_NODE_CODE",
|
|
41
|
+
(identifier: string) => `A node with the same code already exists: ${identifier}`,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
export const ParentNodeNotFoundError = createDomainError(
|
|
45
|
+
"ParentNodeNotFoundError", "PARENT_NODE_NOT_FOUND",
|
|
46
|
+
(identifier: string) => `Specified parent node ID does not exist: ${identifier}`,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export const MaxDepthExceededError = createDomainError(
|
|
50
|
+
"MaxDepthExceededError", "MAX_DEPTH_EXCEEDED",
|
|
51
|
+
(identifier: string) => `Adding this child would exceed the configurable tree depth limit: ${identifier}`,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
export const DeleteNonDraftError = createDomainError(
|
|
55
|
+
"DeleteNonDraftError", "DELETE_NON_DRAFT",
|
|
56
|
+
(identifier: string) => `Item is in ACTIVE or INACTIVE status — only DRAFT items can be deleted: ${identifier}`,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
export const NodeHasChildrenError = createDomainError(
|
|
60
|
+
"NodeHasChildrenError", "NODE_HAS_CHILDREN",
|
|
61
|
+
(identifier: string) => `Node has one or more child nodes — children must be moved or deleted first: ${identifier}`,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
export const NodeHasAssignmentsError = createDomainError(
|
|
65
|
+
"NodeHasAssignmentsError", "NODE_HAS_ASSIGNMENTS",
|
|
66
|
+
(identifier: string) => `Node has one or more item assignments — assignments must be removed first: ${identifier}`,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
export const CircularReferenceError = createDomainError(
|
|
70
|
+
"CircularReferenceError", "CIRCULAR_REFERENCE",
|
|
71
|
+
(identifier: string) => `New parent is the node itself or one of its descendants: ${identifier}`,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
export const AssignmentNotFoundError = createDomainError(
|
|
75
|
+
"AssignmentNotFoundError", "ASSIGNMENT_NOT_FOUND",
|
|
76
|
+
(identifier: string) => `No assignment exists for the specified item and taxonomy node combination: ${identifier}`,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
export const SkuImmutableError = createDomainError(
|
|
80
|
+
"SkuImmutableError", "SKU_IMMUTABLE",
|
|
81
|
+
(identifier: string) => `Attempt to change the SKU field: ${identifier}`,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
export const UomLockedError = createDomainError(
|
|
85
|
+
"UomLockedError", "UOM_LOCKED",
|
|
86
|
+
(identifier: string) => `UoM cannot be changed after item has been activated (status is ACTIVE or INACTIVE): ${identifier}`,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
export const NoFieldsToUpdateError = createDomainError(
|
|
90
|
+
"NoFieldsToUpdateError", "NO_FIELDS_TO_UPDATE",
|
|
91
|
+
(identifier: string) => `No updatable fields were provided in the request: ${identifier}`,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export const CodeImmutableError = createDomainError(
|
|
95
|
+
"CodeImmutableError", "CODE_IMMUTABLE",
|
|
96
|
+
(identifier: string) => `Attempt to change the node code: ${identifier}`,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
export const MissingRequiredFieldsError = createDomainError(
|
|
100
|
+
"MissingRequiredFieldsError", "MISSING_REQUIRED_FIELDS",
|
|
101
|
+
(identifier: string) => `Name not provided or empty: ${identifier}`,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
export const SelfReferenceError = createDomainError(
|
|
105
|
+
"SelfReferenceError", "SELF_REFERENCE",
|
|
106
|
+
(identifier: string) => `Node is moved to itself as parent — returns isCircular: true: ${identifier}`,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
export const NoAssignmentsError = createDomainError(
|
|
110
|
+
"NoAssignmentsError", "NO_ASSIGNMENTS",
|
|
111
|
+
(identifier: string) => `No item assignments exist for the given node — caller receives empty array: ${identifier}`,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
export const NoChildrenError = createDomainError(
|
|
115
|
+
"NoChildrenError", "NO_CHILDREN",
|
|
116
|
+
(identifier: string) => `No child nodes exist for the given parent — caller receives empty array: ${identifier}`,
|
|
117
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { definePermissions } from "../../shared/internal";
|
|
3
|
+
|
|
4
|
+
export const { permissions, own, all } = definePermissions("item-management", [
|
|
5
|
+
"activateItem",
|
|
6
|
+
"assignItemToTaxonomy",
|
|
7
|
+
"createItem",
|
|
8
|
+
"createTaxonomyNode",
|
|
9
|
+
"deactivateItem",
|
|
10
|
+
"deleteItem",
|
|
11
|
+
"deleteTaxonomyNode",
|
|
12
|
+
"moveTaxonomyNode",
|
|
13
|
+
"reactivateItem",
|
|
14
|
+
"removeItemFromTaxonomy",
|
|
15
|
+
"updateItem",
|
|
16
|
+
"updateTaxonomyNode",
|
|
17
|
+
] as const);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { InferSchema, Selectable, Insertable, Updateable } from "../../shared/internal";
|
|
2
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
3
|
+
|
|
4
|
+
export type Schema = InferSchema<DB>;
|
|
5
|
+
|
|
6
|
+
export type Item<T extends { Item: object }> = Selectable<T["Item"]>;
|
|
7
|
+
export type ItemCreate<T extends { Item: object }> = Insertable<T["Item"]>;
|
|
8
|
+
export type ItemUpdate<T extends { Item: object }> = Updateable<T["Item"]>;
|
|
9
|
+
|
|
10
|
+
export type TaxonomyNode<T extends { TaxonomyNode: object }> = Selectable<T["TaxonomyNode"]>;
|
|
11
|
+
export type TaxonomyNodeCreate<T extends { TaxonomyNode: object }> = Insertable<T["TaxonomyNode"]>;
|
|
12
|
+
export type TaxonomyNodeUpdate<T extends { TaxonomyNode: object }> = Updateable<T["TaxonomyNode"]>;
|
|
13
|
+
|
|
14
|
+
export type ItemTaxonomyAssignment<T extends { ItemTaxonomyAssignment: object }> = Selectable<
|
|
15
|
+
T["ItemTaxonomyAssignment"]
|
|
16
|
+
>;
|
|
17
|
+
export type ItemTaxonomyAssignmentCreate<T extends { ItemTaxonomyAssignment: object }> = Insertable<
|
|
18
|
+
T["ItemTaxonomyAssignment"]
|
|
19
|
+
>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type TailorAnyDBField } from "@tailor-platform/sdk";
|
|
2
|
+
import { type EmptyFields, type FieldsToInsertable } from "../shared/internal";
|
|
3
|
+
import type { defineModule as definePrimitivesModule } from "../primitives";
|
|
4
|
+
import { createItem } from "./command/createItem.generated";
|
|
5
|
+
import { updateItem } from "./command/updateItem.generated";
|
|
6
|
+
import { activateItem } from "./command/activateItem.generated";
|
|
7
|
+
import { deactivateItem } from "./command/deactivateItem.generated";
|
|
8
|
+
import { reactivateItem } from "./command/reactivateItem.generated";
|
|
9
|
+
import { deleteItem } from "./command/deleteItem.generated";
|
|
10
|
+
import { createTaxonomyNode } from "./command/createTaxonomyNode.generated";
|
|
11
|
+
import { updateTaxonomyNode } from "./command/updateTaxonomyNode.generated";
|
|
12
|
+
import { moveTaxonomyNode } from "./command/moveTaxonomyNode.generated";
|
|
13
|
+
import { deleteTaxonomyNode } from "./command/deleteTaxonomyNode.generated";
|
|
14
|
+
import { assignItemToTaxonomy } from "./command/assignItemToTaxonomy.generated";
|
|
15
|
+
import { removeItemFromTaxonomy } from "./command/removeItemFromTaxonomy.generated";
|
|
16
|
+
import { getItem } from "./query/getItem.generated";
|
|
17
|
+
import { getTaxonomyNode } from "./query/getTaxonomyNode.generated";
|
|
18
|
+
import { getTaxonomyNodeChildren } from "./query/getTaxonomyNodeChildren.generated";
|
|
19
|
+
import { getItemTaxonomyAssignment } from "./query/getItemTaxonomyAssignment.generated";
|
|
20
|
+
import { getTaxonomyNodeAssignments } from "./query/getTaxonomyNodeAssignments.generated";
|
|
21
|
+
import { calculateNodeDepth } from "./query/calculateNodeDepth.generated";
|
|
22
|
+
import { calculateSubtreeDepth } from "./query/calculateSubtreeDepth.generated";
|
|
23
|
+
import { detectCircularReference } from "./query/detectCircularReference.generated";
|
|
24
|
+
import { createItemType, type CreateItemTypeParams } from "./db/item";
|
|
25
|
+
import { createTaxonomyNodeType, type CreateTaxonomyNodeTypeParams } from "./db/taxonomyNode";
|
|
26
|
+
import {
|
|
27
|
+
createItemTaxonomyAssignmentType,
|
|
28
|
+
type CreateItemTaxonomyAssignmentTypeParams,
|
|
29
|
+
} from "./db/itemTaxonomyAssignment";
|
|
30
|
+
|
|
31
|
+
type PrimitivesModule = ReturnType<typeof definePrimitivesModule>;
|
|
32
|
+
type PrimitivesDB = PrimitivesModule["db"];
|
|
33
|
+
export type PrimitivesQueries = PrimitivesModule["queries"];
|
|
34
|
+
|
|
35
|
+
const DEFAULT_MAX_DEPTH = 10;
|
|
36
|
+
|
|
37
|
+
export interface DefineModuleParams<
|
|
38
|
+
IF extends Record<string, TailorAnyDBField>,
|
|
39
|
+
TNF extends Record<string, TailorAnyDBField>,
|
|
40
|
+
ITAF extends Record<string, TailorAnyDBField>,
|
|
41
|
+
> {
|
|
42
|
+
item?: CreateItemTypeParams<IF>;
|
|
43
|
+
taxonomyNode?: CreateTaxonomyNodeTypeParams<TNF>;
|
|
44
|
+
itemTaxonomyAssignment?: CreateItemTaxonomyAssignmentTypeParams<ITAF>;
|
|
45
|
+
taxonomy?: { maxDepth?: number };
|
|
46
|
+
primitives: {
|
|
47
|
+
db: { unit: PrimitivesDB["unit"] };
|
|
48
|
+
queries: { getUnit: PrimitivesQueries["getUnit"] };
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const defineModule = <
|
|
53
|
+
const IF extends Record<string, TailorAnyDBField> = EmptyFields,
|
|
54
|
+
const TNF extends Record<string, TailorAnyDBField> = EmptyFields,
|
|
55
|
+
const ITAF extends Record<string, TailorAnyDBField> = EmptyFields,
|
|
56
|
+
>(
|
|
57
|
+
params: DefineModuleParams<IF, TNF, ITAF>,
|
|
58
|
+
) => {
|
|
59
|
+
const item = createItemType({
|
|
60
|
+
...params.item,
|
|
61
|
+
unitType: params.primitives.db.unit,
|
|
62
|
+
});
|
|
63
|
+
const taxonomyNode = createTaxonomyNodeType(params.taxonomyNode ?? {});
|
|
64
|
+
const itemTaxonomyAssignment = createItemTaxonomyAssignmentType(
|
|
65
|
+
params.itemTaxonomyAssignment ?? {},
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const maxDepth = params.taxonomy?.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
db: { item, taxonomyNode, itemTaxonomyAssignment },
|
|
72
|
+
queries: {
|
|
73
|
+
getItem,
|
|
74
|
+
getTaxonomyNode,
|
|
75
|
+
getTaxonomyNodeChildren,
|
|
76
|
+
getItemTaxonomyAssignment,
|
|
77
|
+
getTaxonomyNodeAssignments,
|
|
78
|
+
calculateNodeDepth,
|
|
79
|
+
calculateSubtreeDepth,
|
|
80
|
+
detectCircularReference,
|
|
81
|
+
},
|
|
82
|
+
commands: {
|
|
83
|
+
createItem: createItem<FieldsToInsertable<IF>>(params.primitives.queries),
|
|
84
|
+
updateItem: updateItem<FieldsToInsertable<IF>>(params.primitives.queries),
|
|
85
|
+
activateItem: activateItem(),
|
|
86
|
+
deactivateItem: deactivateItem(),
|
|
87
|
+
reactivateItem: reactivateItem(),
|
|
88
|
+
deleteItem: deleteItem(),
|
|
89
|
+
createTaxonomyNode: createTaxonomyNode<FieldsToInsertable<TNF>>({ maxDepth }),
|
|
90
|
+
updateTaxonomyNode: updateTaxonomyNode<FieldsToInsertable<TNF>>(),
|
|
91
|
+
moveTaxonomyNode: moveTaxonomyNode({ maxDepth }),
|
|
92
|
+
deleteTaxonomyNode: deleteTaxonomyNode(),
|
|
93
|
+
assignItemToTaxonomy: assignItemToTaxonomy(),
|
|
94
|
+
removeItemFromTaxonomy: removeItemFromTaxonomy(),
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../testing/index";
|
|
3
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
4
|
+
import { baseRootNode, baseChildNode, baseLeafNode } from "../testing/fixtures";
|
|
5
|
+
import { run } from "./calculateNodeDepth";
|
|
6
|
+
|
|
7
|
+
describe("calculateNodeDepth", () => {
|
|
8
|
+
it("returns depth 1 for root node", async () => {
|
|
9
|
+
const { db, spies } = createMockDb<DB>();
|
|
10
|
+
spies.select.mockReturnValueOnce(baseRootNode);
|
|
11
|
+
|
|
12
|
+
const result = await run(db, { nodeId: "node-1" });
|
|
13
|
+
|
|
14
|
+
expect(result.depth).toBe(1);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns depth 2 for child of root", async () => {
|
|
18
|
+
const { db, spies } = createMockDb<DB>();
|
|
19
|
+
spies.select.mockReturnValueOnce(baseChildNode).mockReturnValueOnce(baseRootNode);
|
|
20
|
+
|
|
21
|
+
const result = await run(db, { nodeId: "node-2" });
|
|
22
|
+
|
|
23
|
+
expect(result.depth).toBe(2);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("returns depth 3 for grandchild of root", async () => {
|
|
27
|
+
const { db, spies } = createMockDb<DB>();
|
|
28
|
+
spies.select
|
|
29
|
+
.mockReturnValueOnce(baseLeafNode)
|
|
30
|
+
.mockReturnValueOnce(baseChildNode)
|
|
31
|
+
.mockReturnValueOnce(baseRootNode);
|
|
32
|
+
|
|
33
|
+
const result = await run(db, { nodeId: "node-3" });
|
|
34
|
+
|
|
35
|
+
expect(result.depth).toBe(3);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns depth 1 when node not found", async () => {
|
|
39
|
+
const { db, spies } = createMockDb<DB>();
|
|
40
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
41
|
+
|
|
42
|
+
const result = await run(db, { nodeId: "nonexistent" });
|
|
43
|
+
|
|
44
|
+
expect(result.depth).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("handles broken ancestor chain gracefully", async () => {
|
|
48
|
+
const brokenNode = { ...baseChildNode, parentId: "missing-parent" };
|
|
49
|
+
const { db, spies } = createMockDb<DB>();
|
|
50
|
+
spies.select.mockReturnValueOnce(brokenNode).mockReturnValueOnce(undefined);
|
|
51
|
+
|
|
52
|
+
const result = await run(db, { nodeId: "node-2" });
|
|
53
|
+
|
|
54
|
+
expect(result.depth).toBe(2);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ReadonlyDB } from "../../shared/internal";
|
|
2
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
3
|
+
|
|
4
|
+
export interface CalculateNodeDepthInput {
|
|
5
|
+
nodeId: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function run(db: ReadonlyDB<DB>, input: CalculateNodeDepthInput) {
|
|
9
|
+
let depth = 1;
|
|
10
|
+
let currentId: string | null = input.nodeId;
|
|
11
|
+
|
|
12
|
+
while (currentId) {
|
|
13
|
+
const node = await db
|
|
14
|
+
.selectFrom("TaxonomyNode")
|
|
15
|
+
.selectAll()
|
|
16
|
+
.where("id", "=", currentId)
|
|
17
|
+
.executeTakeFirst();
|
|
18
|
+
|
|
19
|
+
if (!node) break;
|
|
20
|
+
|
|
21
|
+
if (!node.parentId) break;
|
|
22
|
+
|
|
23
|
+
depth++;
|
|
24
|
+
currentId = node.parentId;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { depth };
|
|
28
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../testing/index";
|
|
3
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
4
|
+
import { baseChildNode, baseLeafNode } from "../testing/fixtures";
|
|
5
|
+
import { run } from "./calculateSubtreeDepth";
|
|
6
|
+
|
|
7
|
+
describe("calculateSubtreeDepth", () => {
|
|
8
|
+
it("returns 1 for leaf node (no children)", async () => {
|
|
9
|
+
const { db, spies } = createMockDb<DB>();
|
|
10
|
+
spies.select.mockReturnValueOnce([]);
|
|
11
|
+
|
|
12
|
+
const result = await run(db, { nodeId: "node-3" });
|
|
13
|
+
|
|
14
|
+
expect(result.depth).toBe(1);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns 2 for node with one level of children", async () => {
|
|
18
|
+
const { db, spies } = createMockDb<DB>();
|
|
19
|
+
spies.select.mockReturnValueOnce([baseLeafNode]).mockReturnValueOnce([]);
|
|
20
|
+
|
|
21
|
+
const result = await run(db, { nodeId: "node-1" });
|
|
22
|
+
|
|
23
|
+
expect(result.depth).toBe(2);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("returns 3 for node with two levels of children", async () => {
|
|
27
|
+
const { db, spies } = createMockDb<DB>();
|
|
28
|
+
spies.select
|
|
29
|
+
.mockReturnValueOnce([baseChildNode])
|
|
30
|
+
.mockReturnValueOnce([baseLeafNode])
|
|
31
|
+
.mockReturnValueOnce([]);
|
|
32
|
+
|
|
33
|
+
const result = await run(db, { nodeId: "node-1" });
|
|
34
|
+
|
|
35
|
+
expect(result.depth).toBe(3);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns max depth for node with multiple branches", async () => {
|
|
39
|
+
const shortChild = {
|
|
40
|
+
id: "short-1",
|
|
41
|
+
code: "S",
|
|
42
|
+
name: "Short",
|
|
43
|
+
parentId: "node-1",
|
|
44
|
+
createdAt: new Date("2024-01-01T00:00:00.000Z"),
|
|
45
|
+
updatedAt: null,
|
|
46
|
+
};
|
|
47
|
+
const deepChild = {
|
|
48
|
+
id: "deep-1",
|
|
49
|
+
code: "D",
|
|
50
|
+
name: "Deep",
|
|
51
|
+
parentId: "node-1",
|
|
52
|
+
createdAt: new Date("2024-01-01T00:00:00.000Z"),
|
|
53
|
+
updatedAt: null,
|
|
54
|
+
};
|
|
55
|
+
const deepGrandchild = {
|
|
56
|
+
id: "deep-2",
|
|
57
|
+
code: "DG",
|
|
58
|
+
name: "Deep Grand",
|
|
59
|
+
parentId: "deep-1",
|
|
60
|
+
createdAt: new Date("2024-01-01T00:00:00.000Z"),
|
|
61
|
+
updatedAt: null,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const { db, spies } = createMockDb<DB>();
|
|
65
|
+
spies.select
|
|
66
|
+
.mockReturnValueOnce([shortChild, deepChild])
|
|
67
|
+
.mockReturnValueOnce([])
|
|
68
|
+
.mockReturnValueOnce([deepGrandchild])
|
|
69
|
+
.mockReturnValueOnce([]);
|
|
70
|
+
|
|
71
|
+
const result = await run(db, { nodeId: "node-1" });
|
|
72
|
+
|
|
73
|
+
expect(result.depth).toBe(3);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ReadonlyDB } from "../../shared/internal";
|
|
2
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
3
|
+
|
|
4
|
+
export interface CalculateSubtreeDepthInput {
|
|
5
|
+
nodeId: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function getSubtreeDepth(db: ReadonlyDB<DB>, nodeId: string): Promise<number> {
|
|
9
|
+
const children = await db
|
|
10
|
+
.selectFrom("TaxonomyNode")
|
|
11
|
+
.selectAll()
|
|
12
|
+
.where("parentId", "=", nodeId)
|
|
13
|
+
.execute();
|
|
14
|
+
|
|
15
|
+
if (!children || children.length === 0) return 1;
|
|
16
|
+
|
|
17
|
+
let maxChildDepth = 0;
|
|
18
|
+
for (const child of children) {
|
|
19
|
+
const childDepth = await getSubtreeDepth(db, child.id);
|
|
20
|
+
if (childDepth > maxChildDepth) maxChildDepth = childDepth;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return 1 + maxChildDepth;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function run(db: ReadonlyDB<DB>, input: CalculateSubtreeDepthInput) {
|
|
27
|
+
const depth = await getSubtreeDepth(db, input.nodeId);
|
|
28
|
+
return { depth };
|
|
29
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../testing/index";
|
|
3
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
4
|
+
import { baseRootNode, baseChildNode, baseLeafNode } from "../testing/fixtures";
|
|
5
|
+
import { run } from "./detectCircularReference";
|
|
6
|
+
|
|
7
|
+
describe("detectCircularReference", () => {
|
|
8
|
+
it("detects self-reference as circular", async () => {
|
|
9
|
+
const { db } = createMockDb<DB>();
|
|
10
|
+
|
|
11
|
+
const result = await run(db, { nodeId: "node-1", newParentId: "node-1" });
|
|
12
|
+
|
|
13
|
+
expect(result.isCircular).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("detects moving node under its child as circular", async () => {
|
|
17
|
+
const { db, spies } = createMockDb<DB>();
|
|
18
|
+
spies.select.mockReturnValueOnce(baseChildNode);
|
|
19
|
+
|
|
20
|
+
const result = await run(db, { nodeId: "node-1", newParentId: "node-2" });
|
|
21
|
+
|
|
22
|
+
expect(result.isCircular).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("detects moving node under its grandchild as circular", async () => {
|
|
26
|
+
const { db, spies } = createMockDb<DB>();
|
|
27
|
+
spies.select.mockReturnValueOnce(baseLeafNode).mockReturnValueOnce(baseChildNode);
|
|
28
|
+
|
|
29
|
+
const result = await run(db, { nodeId: "node-1", newParentId: "node-3" });
|
|
30
|
+
|
|
31
|
+
expect(result.isCircular).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns not circular for valid move", async () => {
|
|
35
|
+
const secondRoot = { ...baseRootNode, id: "node-4", parentId: null };
|
|
36
|
+
const { db, spies } = createMockDb<DB>();
|
|
37
|
+
spies.select.mockReturnValueOnce(secondRoot);
|
|
38
|
+
|
|
39
|
+
const result = await run(db, { nodeId: "node-3", newParentId: "node-4" });
|
|
40
|
+
|
|
41
|
+
expect(result.isCircular).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("returns not circular when new parent is root", async () => {
|
|
45
|
+
const { db, spies } = createMockDb<DB>();
|
|
46
|
+
spies.select.mockReturnValueOnce(baseRootNode);
|
|
47
|
+
|
|
48
|
+
const result = await run(db, { nodeId: "node-2", newParentId: "node-1" });
|
|
49
|
+
|
|
50
|
+
expect(result.isCircular).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("handles broken ancestor chain gracefully (not circular)", async () => {
|
|
54
|
+
const { db, spies } = createMockDb<DB>();
|
|
55
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
56
|
+
|
|
57
|
+
const result = await run(db, { nodeId: "node-1", newParentId: "missing-node" });
|
|
58
|
+
|
|
59
|
+
expect(result.isCircular).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ReadonlyDB } from "../../shared/internal";
|
|
2
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
3
|
+
|
|
4
|
+
export interface DetectCircularReferenceInput {
|
|
5
|
+
nodeId: string;
|
|
6
|
+
newParentId: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function run(db: ReadonlyDB<DB>, input: DetectCircularReferenceInput) {
|
|
10
|
+
if (input.newParentId === input.nodeId) {
|
|
11
|
+
return { isCircular: true };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Walk up from new parent to root, checking if the moved node appears
|
|
15
|
+
let ancestorId: string | null = input.newParentId;
|
|
16
|
+
while (ancestorId) {
|
|
17
|
+
const ancestor = await db
|
|
18
|
+
.selectFrom("TaxonomyNode")
|
|
19
|
+
.selectAll()
|
|
20
|
+
.where("id", "=", ancestorId)
|
|
21
|
+
.executeTakeFirst();
|
|
22
|
+
|
|
23
|
+
if (!ancestor) break;
|
|
24
|
+
|
|
25
|
+
ancestorId = ancestor.parentId;
|
|
26
|
+
if (ancestorId === input.nodeId) {
|
|
27
|
+
return { isCircular: true };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { isCircular: false };
|
|
32
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../testing/index";
|
|
3
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
4
|
+
import { baseActiveItem, baseDraftItem } from "../testing/fixtures";
|
|
5
|
+
import { run } from "./getItem";
|
|
6
|
+
|
|
7
|
+
describe("getItem", () => {
|
|
8
|
+
describe("by id", () => {
|
|
9
|
+
it("returns item when found", async () => {
|
|
10
|
+
const { db, spies } = createMockDb<DB>();
|
|
11
|
+
spies.select.mockReturnValue(baseDraftItem);
|
|
12
|
+
|
|
13
|
+
const result = await run(db, { id: "item-1" });
|
|
14
|
+
|
|
15
|
+
expect(result.item).toEqual(baseDraftItem);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("returns null when item not found", async () => {
|
|
19
|
+
const { db, spies } = createMockDb<DB>();
|
|
20
|
+
spies.select.mockReturnValue(undefined);
|
|
21
|
+
|
|
22
|
+
const result = await run(db, { id: "nonexistent" });
|
|
23
|
+
|
|
24
|
+
expect(result.item).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("by sku", () => {
|
|
29
|
+
it("returns item when found", async () => {
|
|
30
|
+
const { db, spies } = createMockDb<DB>();
|
|
31
|
+
spies.select.mockReturnValue(baseDraftItem);
|
|
32
|
+
|
|
33
|
+
const result = await run(db, { sku: "SKU-001" });
|
|
34
|
+
|
|
35
|
+
expect(result.item).toEqual(baseDraftItem);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns null when item not found", async () => {
|
|
39
|
+
const { db, spies } = createMockDb<DB>();
|
|
40
|
+
spies.select.mockReturnValue(undefined);
|
|
41
|
+
|
|
42
|
+
const result = await run(db, { sku: "NONEXISTENT" });
|
|
43
|
+
|
|
44
|
+
expect(result.item).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("by barcode", () => {
|
|
49
|
+
it("returns item when found", async () => {
|
|
50
|
+
const { db, spies } = createMockDb<DB>();
|
|
51
|
+
spies.select.mockReturnValue(baseActiveItem);
|
|
52
|
+
|
|
53
|
+
const result = await run(db, { barcode: "BAR-002" });
|
|
54
|
+
|
|
55
|
+
expect(result.item).toEqual(baseActiveItem);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns null when item not found", async () => {
|
|
59
|
+
const { db, spies } = createMockDb<DB>();
|
|
60
|
+
spies.select.mockReturnValue(undefined);
|
|
61
|
+
|
|
62
|
+
const result = await run(db, { barcode: "NONEXISTENT" });
|
|
63
|
+
|
|
64
|
+
expect(result.item).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReadonlyDB } from "../../shared/internal";
|
|
2
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
3
|
+
|
|
4
|
+
export type GetItemInput = { id: string } | { sku: string } | { barcode: string };
|
|
5
|
+
|
|
6
|
+
export async function run(db: ReadonlyDB<DB>, input: GetItemInput) {
|
|
7
|
+
let query = db.selectFrom("Item").selectAll();
|
|
8
|
+
|
|
9
|
+
if ("id" in input) {
|
|
10
|
+
query = query.where("id", "=", input.id);
|
|
11
|
+
} else if ("sku" in input) {
|
|
12
|
+
query = query.where("sku", "=", input.sku);
|
|
13
|
+
} else {
|
|
14
|
+
query = query.where("barcode", "=", input.barcode);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const item = await query.executeTakeFirst();
|
|
18
|
+
|
|
19
|
+
return { item: item ?? null };
|
|
20
|
+
}
|