@tailor-platform/erp-kit 0.1.1 → 0.2.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.
- package/CHANGELOG.md +12 -0
- package/README.md +158 -62
- package/dist/cli.js +1010 -270
- package/package.json +11 -8
- package/schemas/module/command.yml +1 -0
- package/schemas/module/model.yml +14 -0
- package/schemas/module/query.yml +53 -0
- package/skills/{app-compose-1-requirement-analysis → erp-kit-app-1-requirements}/SKILL.md +2 -2
- package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/SKILL.md +3 -3
- package/skills/{app-compose-3-doc-review → erp-kit-app-3-doc-review}/SKILL.md +2 -2
- package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/SKILL.md +3 -3
- package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/SKILL.md +4 -4
- package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/SKILL.md +3 -3
- 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 -117
- package/src/commands/app/index.ts +74 -0
- package/src/commands/check.test.ts +3 -2
- package/src/commands/check.ts +3 -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 +87 -0
- package/src/commands/module/list.test.ts +57 -0
- package/src/commands/module/list.ts +64 -0
- package/src/commands/scaffold-templates.ts +65 -0
- package/src/commands/scaffold.test.ts +97 -2
- package/src/commands/scaffold.ts +24 -3
- package/src/commands/sync-check.test.ts +88 -1
- package/src/commands/sync-check.ts +41 -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 +6 -8
- package/src/module.ts +10 -9
- 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/models/Currency.md +4 -0
- package/src/modules/primitives/docs/models/ExchangeRate.md +4 -1
- package/src/modules/primitives/docs/models/Unit.md +4 -1
- package/src/modules/primitives/docs/models/UoMCategory.md +2 -0
- package/src/modules/primitives/docs/{commands → queries}/ConvertAmount.md +3 -5
- package/src/modules/primitives/docs/{commands → 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 +17 -6
- package/src/modules/primitives/lib/errors.generated.ts +112 -0
- package/src/modules/primitives/{permissions.ts → lib/permissions.generated.ts} +9 -10
- package/src/modules/primitives/module.ts +39 -27
- package/src/modules/primitives/query/convertAmount.generated.ts +5 -0
- package/src/modules/primitives/{command → query}/convertAmount.test.ts +4 -21
- package/src/modules/primitives/query/convertAmount.ts +121 -0
- package/src/modules/primitives/query/convertQuantity.generated.ts +5 -0
- package/src/modules/primitives/{command → query}/convertQuantity.test.ts +8 -15
- package/src/modules/primitives/query/convertQuantity.ts +63 -0
- 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/defineQuery.test.ts +28 -0
- package/src/modules/shared/defineQuery.ts +16 -0
- package/src/modules/shared/internal.ts +3 -1
- 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/shared/types.ts +8 -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/docs/models/AuditEvent.md +2 -0
- package/src/modules/user-management/docs/models/Permission.md +2 -0
- package/src/modules/user-management/docs/models/Role.md +2 -0
- package/src/modules/user-management/docs/models/RolePermission.md +2 -0
- package/src/modules/user-management/docs/models/User.md +2 -0
- package/src/modules/user-management/docs/models/UserRole.md +2 -0
- 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 +2 -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/src/modules/primitives/command/convertAmount.ts +0 -126
- package/src/modules/primitives/command/convertQuantity.ts +0 -73
- package/src/modules/primitives/lib/errors.ts +0 -138
- package/src/modules/user-management/lib/errors.ts +0 -81
- /package/skills/{app-compose-1-requirement-analysis → erp-kit-app-1-requirements}/references/structure.md +0 -0
- /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-detailview.md +0 -0
- /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-form.md +0 -0
- /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-listview.md +0 -0
- /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/structure.md +0 -0
- /package/skills/{app-compose-3-doc-review → erp-kit-app-3-doc-review}/references/structure.md +0 -0
- /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/component.md +0 -0
- /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-detailview.md +0 -0
- /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-form.md +0 -0
- /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-listview.md +0 -0
- /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/structure.md +0 -0
- /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/component.md +0 -0
- /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-detailview.md +0 -0
- /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-form.md +0 -0
- /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-listview.md +0 -0
- /package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/references/auth.md +0 -0
- /package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/references/structure.md +0 -0
- /package/skills/{2-module-feature-breakdown → erp-kit-module-4-tdd}/references/models.md +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
2
|
+
import { toString } from "mdast-util-to-string";
|
|
3
|
+
import type { Heading, List, Root, RootContent } from "mdast";
|
|
4
|
+
|
|
5
|
+
export interface ParsedError {
|
|
6
|
+
code: string;
|
|
7
|
+
description: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ParsedDependency {
|
|
11
|
+
module: string;
|
|
12
|
+
entity: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ParsedCommandDoc {
|
|
16
|
+
commandName: string;
|
|
17
|
+
errors: ParsedError[];
|
|
18
|
+
externalDependencies: ParsedDependency[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function parseCommandDoc(fileName: string, markdown: string): ParsedCommandDoc {
|
|
22
|
+
const commandName = fileName.charAt(0).toLowerCase() + fileName.slice(1);
|
|
23
|
+
const tree = fromMarkdown(markdown);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
commandName,
|
|
27
|
+
errors: parseErrorScenarios(tree),
|
|
28
|
+
externalDependencies: parseExternalDependencies(tree),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function errorCodeToClassName(code: string): string {
|
|
33
|
+
const pascal = code
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.split("_")
|
|
36
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
37
|
+
.join("");
|
|
38
|
+
return pascal + "Error";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isHeading(node: RootContent): node is Heading {
|
|
42
|
+
return node.type === "heading";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isList(node: RootContent): node is List {
|
|
46
|
+
return node.type === "list";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getNodesUnderHeading(tree: Root, headingText: string): RootContent[] {
|
|
50
|
+
const nodes: RootContent[] = [];
|
|
51
|
+
let collecting = false;
|
|
52
|
+
|
|
53
|
+
for (const node of tree.children) {
|
|
54
|
+
if (isHeading(node)) {
|
|
55
|
+
if (collecting) break;
|
|
56
|
+
if (node.depth === 2 && toString(node) === headingText) {
|
|
57
|
+
collecting = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (collecting) {
|
|
62
|
+
nodes.push(node);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return nodes;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const ERROR_PATTERN = /^([A-Z_]+):\s*(.+)$/;
|
|
70
|
+
|
|
71
|
+
function parseErrorScenarios(tree: Root): ParsedError[] {
|
|
72
|
+
const nodes = getNodesUnderHeading(tree, "Error Scenarios");
|
|
73
|
+
const errors: ParsedError[] = [];
|
|
74
|
+
|
|
75
|
+
for (const node of nodes) {
|
|
76
|
+
if (!isList(node)) continue;
|
|
77
|
+
|
|
78
|
+
for (const item of node.children) {
|
|
79
|
+
const text = toString(item);
|
|
80
|
+
const match = ERROR_PATTERN.exec(text);
|
|
81
|
+
if (match) {
|
|
82
|
+
errors.push({ code: match[1], description: match[2].trim() });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return errors;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const DEPENDENCY_PATTERN = /^([^:]+)::(.+)$/;
|
|
91
|
+
|
|
92
|
+
function parseExternalDependencies(tree: Root): ParsedDependency[] {
|
|
93
|
+
const nodes = getNodesUnderHeading(tree, "External Dependencies");
|
|
94
|
+
const deps: ParsedDependency[] = [];
|
|
95
|
+
|
|
96
|
+
for (const node of nodes) {
|
|
97
|
+
if (!isList(node)) continue;
|
|
98
|
+
|
|
99
|
+
for (const item of node.children) {
|
|
100
|
+
const firstChild = item.children[0];
|
|
101
|
+
if (firstChild?.type !== "paragraph") continue;
|
|
102
|
+
|
|
103
|
+
for (const inline of firstChild.children) {
|
|
104
|
+
if (inline.type === "link" || inline.type === "linkReference") {
|
|
105
|
+
const linkText = toString(inline);
|
|
106
|
+
const match = DEPENDENCY_PATTERN.exec(linkText);
|
|
107
|
+
if (match) {
|
|
108
|
+
deps.push({ module: match[1], entity: match[2] });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return deps;
|
|
116
|
+
}
|
package/src/integration.test.ts
CHANGED
|
@@ -11,13 +11,11 @@ const REPO_ROOT = path.resolve(__dirname, "..", "..", "..");
|
|
|
11
11
|
const skipReason = fs.existsSync(CLI_PATH) ? undefined : "Run `pnpm build` first";
|
|
12
12
|
|
|
13
13
|
describe.skipIf(skipReason)("integration", () => {
|
|
14
|
-
it("check command runs against
|
|
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
|
|
14
|
+
it("module check command runs against erp-kit modules", () => {
|
|
17
15
|
try {
|
|
18
16
|
const result = execFileSync(
|
|
19
17
|
"node",
|
|
20
|
-
[CLI_PATH, "check", "--
|
|
18
|
+
[CLI_PATH, "module", "check", "--root", "packages/erp-kit/src/modules"],
|
|
21
19
|
{
|
|
22
20
|
cwd: REPO_ROOT,
|
|
23
21
|
encoding: "utf-8",
|
|
@@ -33,11 +31,11 @@ describe.skipIf(skipReason)("integration", () => {
|
|
|
33
31
|
}
|
|
34
32
|
});
|
|
35
33
|
|
|
36
|
-
it("sync-check command runs against
|
|
34
|
+
it("module sync-check command runs against erp-kit modules", () => {
|
|
37
35
|
try {
|
|
38
36
|
const result = execFileSync(
|
|
39
37
|
"node",
|
|
40
|
-
[CLI_PATH, "sync-check", "--
|
|
38
|
+
[CLI_PATH, "module", "sync-check", "--root", "packages/erp-kit/src/modules"],
|
|
41
39
|
{
|
|
42
40
|
cwd: REPO_ROOT,
|
|
43
41
|
encoding: "utf-8",
|
|
@@ -57,7 +55,7 @@ describe.skipIf(skipReason)("integration", () => {
|
|
|
57
55
|
encoding: "utf-8",
|
|
58
56
|
});
|
|
59
57
|
expect(result).toContain("erp-kit");
|
|
60
|
-
expect(result).toContain("
|
|
61
|
-
expect(result).toContain("
|
|
58
|
+
expect(result).toContain("module");
|
|
59
|
+
expect(result).toContain("app");
|
|
62
60
|
});
|
|
63
61
|
});
|
package/src/module.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// shared/internal
|
|
2
2
|
export { defineCommand, type Command } from "./modules/shared/defineCommand";
|
|
3
|
+
export { defineQuery, type Query } from "./modules/shared/defineQuery";
|
|
3
4
|
export { definePermissions } from "./modules/shared/definePermissions";
|
|
4
5
|
export { requirePermission } from "./modules/shared/requirePermission";
|
|
5
6
|
export { createDomainError, InsufficientPermissionError } from "./modules/shared/errors";
|
|
6
|
-
export type { CommandContext } from "./modules/shared/types";
|
|
7
|
+
export type { CommandContext, QueryContext, ReadonlyDB } from "./modules/shared/types";
|
|
7
8
|
export type {
|
|
8
9
|
InferSchema,
|
|
9
10
|
Selectable,
|
|
@@ -18,9 +19,9 @@ export {
|
|
|
18
19
|
permissions as primitivesPermissions,
|
|
19
20
|
own as primitivesOwn,
|
|
20
21
|
all as primitivesAll,
|
|
21
|
-
} from "./modules/primitives/permissions";
|
|
22
|
+
} from "./modules/primitives/lib/permissions.generated";
|
|
22
23
|
export {
|
|
23
|
-
|
|
24
|
+
UomCategoryNotFoundError,
|
|
24
25
|
UnitNotFoundError,
|
|
25
26
|
IncompatibleUnitsError,
|
|
26
27
|
InactiveUnitError,
|
|
@@ -35,21 +36,21 @@ export {
|
|
|
35
36
|
DuplicateCategoryNameError,
|
|
36
37
|
CategoryHasActiveUnitsError,
|
|
37
38
|
UnitNotInCategoryError,
|
|
38
|
-
|
|
39
|
+
InvalidIsoCodeError,
|
|
39
40
|
DuplicateCurrencyCodeError,
|
|
40
41
|
InvalidDecimalPlacesError,
|
|
41
42
|
CannotDeactivateBaseCurrencyError,
|
|
42
43
|
CannotSetInactiveAsBaseCurrencyError,
|
|
43
44
|
SameCurrencyPairError,
|
|
44
45
|
InvalidExchangeRateError,
|
|
45
|
-
} from "./modules/primitives/lib/errors";
|
|
46
|
-
export { type ConvertQuantityInput } from "./modules/primitives/
|
|
46
|
+
} from "./modules/primitives/lib/errors.generated";
|
|
47
|
+
export { type ConvertQuantityInput } from "./modules/primitives/query/convertQuantity";
|
|
47
48
|
export { type ActivateCategoryInput } from "./modules/primitives/command/activateCategory";
|
|
48
49
|
export { type DeactivateCategoryInput } from "./modules/primitives/command/deactivateCategory";
|
|
49
50
|
export { type SetReferenceUnitInput } from "./modules/primitives/command/setReferenceUnit";
|
|
50
51
|
export { type ActivateUnitInput } from "./modules/primitives/command/activateUnit";
|
|
51
52
|
export { type DeactivateUnitInput } from "./modules/primitives/command/deactivateUnit";
|
|
52
|
-
export { type ConvertAmountInput } from "./modules/primitives/
|
|
53
|
+
export { type ConvertAmountInput } from "./modules/primitives/query/convertAmount";
|
|
53
54
|
export { type ActivateCurrencyInput } from "./modules/primitives/command/activateCurrency";
|
|
54
55
|
export { type DeactivateCurrencyInput } from "./modules/primitives/command/deactivateCurrency";
|
|
55
56
|
export { type SetBaseCurrencyInput } from "./modules/primitives/command/setBaseCurrency";
|
|
@@ -59,7 +60,7 @@ export {
|
|
|
59
60
|
permissions as userManagementPermissions,
|
|
60
61
|
own as userManagementOwn,
|
|
61
62
|
all as userManagementAll,
|
|
62
|
-
} from "./modules/user-management/permissions";
|
|
63
|
+
} from "./modules/user-management/lib/permissions.generated";
|
|
63
64
|
export { AuditEventEventType, UserStatus } from "./modules/user-management/generated/enums";
|
|
64
65
|
export {
|
|
65
66
|
UserNotFoundError,
|
|
@@ -75,7 +76,7 @@ export {
|
|
|
75
76
|
DuplicateRoleNameError,
|
|
76
77
|
AssignmentNotFoundError,
|
|
77
78
|
InvalidEventTypeError,
|
|
78
|
-
} from "./modules/user-management/lib/errors";
|
|
79
|
+
} from "./modules/user-management/lib/errors.generated";
|
|
79
80
|
export { type ActivateUserInput } from "./modules/user-management/command/activateUser";
|
|
80
81
|
export { type DeactivateUserInput } from "./modules/user-management/command/deactivateUser";
|
|
81
82
|
export { type ReactivateUserInput } from "./modules/user-management/command/reactivateUser";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# README
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Item Management module manages items — the fundamental SKU-level entity in the ERP system. Items are the sellable, purchasable, and trackable unit that all downstream modules (Sales, Purchasing, Inventory, Manufacturing) reference as the primary transactional entity.
|
|
6
|
+
|
|
7
|
+
Each item has its own lifecycle (DRAFT → ACTIVE ↔ INACTIVE), unique SKU identity, and optional barcode. Items are classified through a flexible taxonomy system (many-to-many with TaxonomyNode) for hierarchical browsing and filtering.
|
|
8
|
+
|
|
9
|
+
## Key Features
|
|
10
|
+
|
|
11
|
+
- **Item Lifecycle**: The canonical SKU-level entity referenced by all operational modules. Lifecycle state management (DRAFT → ACTIVE ↔ INACTIVE), SKU and barcode assignment. Items can be created directly or generated from product templates
|
|
12
|
+
- **Item Taxonomy**: Hierarchical taxonomy tree for multi-dimensional item classification (many-to-many). Items can belong to multiple taxonomy branches simultaneously
|
|
13
|
+
|
|
14
|
+
## Module Scope
|
|
15
|
+
|
|
16
|
+
### In Scope
|
|
17
|
+
|
|
18
|
+
- Item master data management (create, update, delete, status transitions)
|
|
19
|
+
- Item lifecycle state machine (DRAFT → ACTIVE ↔ INACTIVE)
|
|
20
|
+
- SKU and barcode assignment and uniqueness enforcement
|
|
21
|
+
- Unit of measure assignment per item
|
|
22
|
+
- Hierarchical item taxonomy management (TaxonomyNode tree)
|
|
23
|
+
- Item-to-taxonomy classification (many-to-many)
|
|
24
|
+
|
|
25
|
+
### Out of Scope
|
|
26
|
+
|
|
27
|
+
- Product template definitions and attribute schemas (product-management module)
|
|
28
|
+
- Variant generation from attribute combinations (product-management module)
|
|
29
|
+
- Sales price list and pricing rule management (pricing module)
|
|
30
|
+
- Inventory tracking and stock levels (inventory module)
|
|
31
|
+
- Lot and serial number management (inventory module)
|
|
32
|
+
- Vendor/supplier management (supplier-management module)
|
|
33
|
+
- Bill of Materials (manufacturing module)
|
|
34
|
+
- Product images and digital asset management
|
|
35
|
+
|
|
36
|
+
## Module Dependencies
|
|
37
|
+
|
|
38
|
+
- [primitives](../primitives/README.md) — UoM definitions for item unit of measure
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../testing/index";
|
|
3
|
+
import { type CommandContext } from "../../shared/internal";
|
|
4
|
+
import { DB } from "../generated/kysely-tailordb";
|
|
5
|
+
import { InvalidStateTransitionError, ItemNotFoundError } from "../lib/errors.generated";
|
|
6
|
+
import { baseActiveItem, baseDraftItem, baseInactiveItem } from "../testing/fixtures";
|
|
7
|
+
import { run } from "./activateItem";
|
|
8
|
+
|
|
9
|
+
describe("activateItem", () => {
|
|
10
|
+
const ctx: CommandContext = {
|
|
11
|
+
actorId: "test-actor",
|
|
12
|
+
permissions: ["item-management:activateItem"],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
it("returns error when item does not exist", async () => {
|
|
16
|
+
const { db, spies } = createMockDb<DB>();
|
|
17
|
+
spies.select.mockReturnValue(undefined);
|
|
18
|
+
|
|
19
|
+
const result = await run(db, { id: "nonexistent" }, ctx);
|
|
20
|
+
expect(result.ok).toBe(false);
|
|
21
|
+
if (!result.ok) {
|
|
22
|
+
expect(result.error).toBeInstanceOf(ItemNotFoundError);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("returns error when item is not DRAFT", async () => {
|
|
27
|
+
const { db, spies } = createMockDb<DB>();
|
|
28
|
+
spies.select.mockReturnValueOnce(baseActiveItem);
|
|
29
|
+
|
|
30
|
+
const result = await run(db, { id: baseActiveItem.id }, ctx);
|
|
31
|
+
expect(result.ok).toBe(false);
|
|
32
|
+
if (!result.ok) {
|
|
33
|
+
expect(result.error).toBeInstanceOf(InvalidStateTransitionError);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("returns error when item is INACTIVE", async () => {
|
|
38
|
+
const { db, spies } = createMockDb<DB>();
|
|
39
|
+
spies.select.mockReturnValueOnce(baseInactiveItem);
|
|
40
|
+
|
|
41
|
+
const result = await run(db, { id: baseInactiveItem.id }, ctx);
|
|
42
|
+
expect(result.ok).toBe(false);
|
|
43
|
+
if (!result.ok) {
|
|
44
|
+
expect(result.error).toBeInstanceOf(InvalidStateTransitionError);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("activates a DRAFT item", async () => {
|
|
49
|
+
const { db, spies } = createMockDb<DB>();
|
|
50
|
+
const activatedItem = { ...baseDraftItem, status: "ACTIVE" };
|
|
51
|
+
spies.select.mockReturnValueOnce(baseDraftItem);
|
|
52
|
+
spies.update.mockReturnValue(activatedItem);
|
|
53
|
+
|
|
54
|
+
const result = await run(db, { id: baseDraftItem.id }, ctx);
|
|
55
|
+
|
|
56
|
+
expect(result.ok).toBe(true);
|
|
57
|
+
if (result.ok) {
|
|
58
|
+
expect(result.value.item.status).toBe("ACTIVE");
|
|
59
|
+
}
|
|
60
|
+
expect(spies.update).toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("activates from custom status when from is overridden", async () => {
|
|
64
|
+
const { db, spies } = createMockDb<DB>();
|
|
65
|
+
const activatedItem = { ...baseInactiveItem, status: "ACTIVE" };
|
|
66
|
+
spies.select.mockReturnValueOnce(baseInactiveItem);
|
|
67
|
+
spies.update.mockReturnValue(activatedItem);
|
|
68
|
+
|
|
69
|
+
const result = await run(db, { id: baseInactiveItem.id, from: ["DRAFT", "INACTIVE"] }, ctx);
|
|
70
|
+
|
|
71
|
+
expect(result.ok).toBe(true);
|
|
72
|
+
if (result.ok) {
|
|
73
|
+
expect(result.value.item.status).toBe("ACTIVE");
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ok, err, type CommandContext } from "../../shared/internal";
|
|
2
|
+
import { DB } from "../generated/kysely-tailordb";
|
|
3
|
+
import { InvalidStateTransitionError, ItemNotFoundError } from "../lib/errors.generated";
|
|
4
|
+
import { getItem } from "../query/getItem.generated";
|
|
5
|
+
|
|
6
|
+
export interface ActivateItemInput {
|
|
7
|
+
id: string;
|
|
8
|
+
from?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Function: activateItem
|
|
13
|
+
*
|
|
14
|
+
* Transitions an item from DRAFT to ACTIVE status.
|
|
15
|
+
* Requires name, SKU, and UoM to be set.
|
|
16
|
+
*/
|
|
17
|
+
export async function run(db: DB, input: ActivateItemInput, ctx: CommandContext) {
|
|
18
|
+
const { id, from } = input;
|
|
19
|
+
|
|
20
|
+
// 1. Check item exists
|
|
21
|
+
const { item } = await getItem(db, { id }, ctx);
|
|
22
|
+
|
|
23
|
+
if (!item) {
|
|
24
|
+
return err(new ItemNotFoundError(id));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 2. Check status is valid for activation
|
|
28
|
+
const validFromStatuses = from ?? ["DRAFT"];
|
|
29
|
+
if (!validFromStatuses.includes(item.status)) {
|
|
30
|
+
return err(new InvalidStateTransitionError(`${item.status} to ACTIVE`));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 3. Update status to ACTIVE
|
|
34
|
+
const activatedItem = await db
|
|
35
|
+
.updateTable("Item")
|
|
36
|
+
.set({ status: "ACTIVE", updatedAt: new Date() })
|
|
37
|
+
.where("id", "=", id)
|
|
38
|
+
.returningAll()
|
|
39
|
+
.executeTakeFirstOrThrow();
|
|
40
|
+
|
|
41
|
+
return ok({ item: activatedItem });
|
|
42
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { defineCommand } from "../../shared/internal";
|
|
3
|
+
import { permissions } from "../lib/permissions.generated";
|
|
4
|
+
import { run } from "./assignItemToTaxonomy";
|
|
5
|
+
|
|
6
|
+
export const assignItemToTaxonomy = defineCommand(permissions.assignItemToTaxonomy, run);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../testing/index";
|
|
3
|
+
import { type CommandContext } from "../../shared/internal";
|
|
4
|
+
import { DB } from "../generated/kysely-tailordb";
|
|
5
|
+
import {
|
|
6
|
+
DuplicateAssignmentError,
|
|
7
|
+
ItemNotFoundError,
|
|
8
|
+
NodeNotFoundError,
|
|
9
|
+
} from "../lib/errors.generated";
|
|
10
|
+
import { baseActiveItem, baseAssignment, baseChildNode } from "../testing/fixtures";
|
|
11
|
+
import { run } from "./assignItemToTaxonomy";
|
|
12
|
+
|
|
13
|
+
describe("assignItemToTaxonomy", () => {
|
|
14
|
+
const ctx: CommandContext = {
|
|
15
|
+
actorId: "test-actor",
|
|
16
|
+
permissions: ["item-management:assignItemToTaxonomy"],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
it("returns error when item does not exist", async () => {
|
|
20
|
+
const { db, spies } = createMockDb<DB>();
|
|
21
|
+
spies.select.mockReturnValueOnce(undefined); // Item not found
|
|
22
|
+
|
|
23
|
+
const result = await run(db, { itemId: "nonexistent", taxonomyNodeId: baseChildNode.id }, ctx);
|
|
24
|
+
expect(result.ok).toBe(false);
|
|
25
|
+
if (!result.ok) {
|
|
26
|
+
expect(result.error).toBeInstanceOf(ItemNotFoundError);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("returns error when node does not exist", async () => {
|
|
31
|
+
const { db, spies } = createMockDb<DB>();
|
|
32
|
+
spies.select
|
|
33
|
+
.mockReturnValueOnce(baseActiveItem) // Item exists
|
|
34
|
+
.mockReturnValueOnce(undefined); // Node not found
|
|
35
|
+
|
|
36
|
+
const result = await run(db, { itemId: baseActiveItem.id, taxonomyNodeId: "nonexistent" }, ctx);
|
|
37
|
+
expect(result.ok).toBe(false);
|
|
38
|
+
if (!result.ok) {
|
|
39
|
+
expect(result.error).toBeInstanceOf(NodeNotFoundError);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("returns error when assignment already exists", async () => {
|
|
44
|
+
const { db, spies } = createMockDb<DB>();
|
|
45
|
+
spies.select
|
|
46
|
+
.mockReturnValueOnce(baseActiveItem) // Item exists
|
|
47
|
+
.mockReturnValueOnce(baseChildNode) // Node exists
|
|
48
|
+
.mockReturnValueOnce(baseAssignment); // Assignment exists
|
|
49
|
+
|
|
50
|
+
const result = await run(
|
|
51
|
+
db,
|
|
52
|
+
{ itemId: baseActiveItem.id, taxonomyNodeId: baseChildNode.id },
|
|
53
|
+
ctx,
|
|
54
|
+
);
|
|
55
|
+
expect(result.ok).toBe(false);
|
|
56
|
+
if (!result.ok) {
|
|
57
|
+
expect(result.error).toBeInstanceOf(DuplicateAssignmentError);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("creates assignment successfully", async () => {
|
|
62
|
+
const { db, spies } = createMockDb<DB>();
|
|
63
|
+
const createdAssignment = {
|
|
64
|
+
...baseAssignment,
|
|
65
|
+
id: "new-assignment-id",
|
|
66
|
+
itemId: baseActiveItem.id,
|
|
67
|
+
taxonomyNodeId: baseChildNode.id,
|
|
68
|
+
};
|
|
69
|
+
spies.select
|
|
70
|
+
.mockReturnValueOnce(baseActiveItem) // Item exists
|
|
71
|
+
.mockReturnValueOnce(baseChildNode) // Node exists
|
|
72
|
+
.mockReturnValueOnce(undefined); // No existing assignment
|
|
73
|
+
spies.insert.mockReturnValue(createdAssignment);
|
|
74
|
+
|
|
75
|
+
const result = await run(
|
|
76
|
+
db,
|
|
77
|
+
{ itemId: baseActiveItem.id, taxonomyNodeId: baseChildNode.id },
|
|
78
|
+
ctx,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(result.ok).toBe(true);
|
|
82
|
+
if (result.ok) {
|
|
83
|
+
expect(result.value.assignment.itemId).toBe(baseActiveItem.id);
|
|
84
|
+
expect(result.value.assignment.taxonomyNodeId).toBe(baseChildNode.id);
|
|
85
|
+
}
|
|
86
|
+
expect(spies.insert).toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ok, err, type CommandContext } from "../../shared/internal";
|
|
2
|
+
import { DB } from "../generated/kysely-tailordb";
|
|
3
|
+
import {
|
|
4
|
+
DuplicateAssignmentError,
|
|
5
|
+
ItemNotFoundError,
|
|
6
|
+
NodeNotFoundError,
|
|
7
|
+
} from "../lib/errors.generated";
|
|
8
|
+
import { getItem } from "../query/getItem.generated";
|
|
9
|
+
import { getTaxonomyNode } from "../query/getTaxonomyNode.generated";
|
|
10
|
+
import { getItemTaxonomyAssignment } from "../query/getItemTaxonomyAssignment.generated";
|
|
11
|
+
|
|
12
|
+
export interface AssignItemToTaxonomyInput {
|
|
13
|
+
itemId: string;
|
|
14
|
+
taxonomyNodeId: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Function: assignItemToTaxonomy
|
|
19
|
+
*
|
|
20
|
+
* Creates a link between an item and a taxonomy node for many-to-many classification.
|
|
21
|
+
*/
|
|
22
|
+
export async function run(db: DB, input: AssignItemToTaxonomyInput, ctx: CommandContext) {
|
|
23
|
+
const { itemId, taxonomyNodeId } = input;
|
|
24
|
+
|
|
25
|
+
// 1. Check item exists
|
|
26
|
+
const { item } = await getItem(db, { id: itemId }, ctx);
|
|
27
|
+
|
|
28
|
+
if (!item) {
|
|
29
|
+
return err(new ItemNotFoundError(itemId));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 2. Check node exists
|
|
33
|
+
const { node } = await getTaxonomyNode(db, { id: taxonomyNodeId }, ctx);
|
|
34
|
+
|
|
35
|
+
if (!node) {
|
|
36
|
+
return err(new NodeNotFoundError(taxonomyNodeId));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3. Check for duplicate assignment
|
|
40
|
+
const { assignment: existing } = await getItemTaxonomyAssignment(
|
|
41
|
+
db,
|
|
42
|
+
{ itemId, taxonomyNodeId },
|
|
43
|
+
ctx,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (existing) {
|
|
47
|
+
return err(new DuplicateAssignmentError(`${itemId} in ${taxonomyNodeId}`));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 4. Create assignment
|
|
51
|
+
const assignment = await db
|
|
52
|
+
.insertInto("ItemTaxonomyAssignment")
|
|
53
|
+
.values({
|
|
54
|
+
itemId,
|
|
55
|
+
taxonomyNodeId,
|
|
56
|
+
createdAt: new Date(),
|
|
57
|
+
updatedAt: null,
|
|
58
|
+
})
|
|
59
|
+
.returningAll()
|
|
60
|
+
.executeTakeFirstOrThrow();
|
|
61
|
+
|
|
62
|
+
return ok({ assignment: assignment });
|
|
63
|
+
}
|