Initial commit: add .gitignore and README
Some checks failed
CI / lint-and-test (push) Has been cancelled
Some checks failed
CI / lint-and-test (push) Has been cancelled
This commit is contained in:
24
apps/workflow/package.json
Normal file
24
apps/workflow/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@sankofa/workflow",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"test": "vitest run",
|
||||
"lint": "eslint src --ext .ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sankofa/schema": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.0",
|
||||
"eslint": "^9.15.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^2.1.0"
|
||||
}
|
||||
}
|
||||
12
apps/workflow/src/index.test.ts
Normal file
12
apps/workflow/src/index.test.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { canTransitionPO, computeOfferRiskScore } from "./index";
|
||||
|
||||
describe("workflow", () => {
|
||||
it("allows draft to pending_approval", () => {
|
||||
expect(canTransitionPO("draft", "pending_approval")).toBe(true);
|
||||
});
|
||||
it("risk score computed", () => {
|
||||
const { score } = computeOfferRiskScore({ trustTier: "unknown" });
|
||||
expect(score).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
39
apps/workflow/src/index.ts
Normal file
39
apps/workflow/src/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export const PO_STATUS = ["draft", "pending_approval", "approved", "rejected", "ordered", "received"] as const;
|
||||
export type POStatus = (typeof PO_STATUS)[number];
|
||||
|
||||
export const APPROVAL_STAGES = ["requester", "procurement", "finance", "executive"] as const;
|
||||
export type ApprovalStage = (typeof APPROVAL_STAGES)[number];
|
||||
|
||||
export function nextPOStage(current: ApprovalStage | null): ApprovalStage | null {
|
||||
if (!current) return "requester";
|
||||
const i = APPROVAL_STAGES.indexOf(current);
|
||||
return i < APPROVAL_STAGES.length - 1 ? APPROVAL_STAGES[i + 1] : null;
|
||||
}
|
||||
|
||||
export function canTransitionPO(from: POStatus, to: POStatus): boolean {
|
||||
const allowed: Record<POStatus, POStatus[]> = {
|
||||
draft: ["pending_approval", "rejected"],
|
||||
pending_approval: ["approved", "rejected", "draft"],
|
||||
approved: ["ordered"],
|
||||
rejected: ["draft"],
|
||||
ordered: ["received"],
|
||||
received: [],
|
||||
};
|
||||
return allowed[from]?.includes(to) ?? false;
|
||||
}
|
||||
|
||||
export interface RiskFactors {
|
||||
trustTier: string;
|
||||
priceDeviation?: number;
|
||||
conditionAmbiguity?: boolean;
|
||||
}
|
||||
|
||||
export function computeOfferRiskScore(factors: RiskFactors): { score: number; factors: RiskFactors } {
|
||||
let score = 0;
|
||||
if (factors.trustTier === "unknown") score += 30;
|
||||
else if (factors.trustTier === "low") score += 20;
|
||||
else if (factors.trustTier === "medium") score += 10;
|
||||
if (factors.priceDeviation != null && factors.priceDeviation > 0.2) score += 25;
|
||||
if (factors.conditionAmbiguity) score += 20;
|
||||
return { score: Math.min(100, score), factors };
|
||||
}
|
||||
15
apps/workflow/tsconfig.json
Normal file
15
apps/workflow/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user