@hasna/microservices 0.0.4 → 0.0.6
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/bin/index.js +9 -1
- package/bin/mcp.js +9 -1
- package/dist/index.js +9 -1
- package/microservices/microservice-ads/src/cli/index.ts +198 -0
- package/microservices/microservice-ads/src/db/campaigns.ts +304 -0
- package/microservices/microservice-ads/src/mcp/index.ts +160 -0
- package/microservices/microservice-company/package.json +27 -0
- package/microservices/microservice-company/src/cli/index.ts +1126 -0
- package/microservices/microservice-company/src/db/company.ts +854 -0
- package/microservices/microservice-company/src/db/database.ts +93 -0
- package/microservices/microservice-company/src/db/migrations.ts +214 -0
- package/microservices/microservice-company/src/db/workflow-migrations.ts +44 -0
- package/microservices/microservice-company/src/index.ts +60 -0
- package/microservices/microservice-company/src/lib/audit.ts +168 -0
- package/microservices/microservice-company/src/lib/finance.ts +299 -0
- package/microservices/microservice-company/src/lib/settings.ts +85 -0
- package/microservices/microservice-company/src/lib/workflows.ts +698 -0
- package/microservices/microservice-company/src/mcp/index.ts +991 -0
- package/microservices/microservice-contracts/src/cli/index.ts +410 -23
- package/microservices/microservice-contracts/src/db/contracts.ts +430 -1
- package/microservices/microservice-contracts/src/db/migrations.ts +83 -0
- package/microservices/microservice-contracts/src/mcp/index.ts +312 -3
- package/microservices/microservice-domains/src/cli/index.ts +673 -0
- package/microservices/microservice-domains/src/db/domains.ts +613 -0
- package/microservices/microservice-domains/src/index.ts +21 -0
- package/microservices/microservice-domains/src/lib/brandsight.ts +285 -0
- package/microservices/microservice-domains/src/lib/godaddy.ts +328 -0
- package/microservices/microservice-domains/src/lib/namecheap.ts +474 -0
- package/microservices/microservice-domains/src/lib/registrar.ts +355 -0
- package/microservices/microservice-domains/src/mcp/index.ts +413 -0
- package/microservices/microservice-hiring/src/cli/index.ts +318 -8
- package/microservices/microservice-hiring/src/db/hiring.ts +503 -0
- package/microservices/microservice-hiring/src/db/migrations.ts +21 -0
- package/microservices/microservice-hiring/src/index.ts +29 -0
- package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
- package/microservices/microservice-hiring/src/mcp/index.ts +245 -0
- package/microservices/microservice-payments/src/cli/index.ts +255 -3
- package/microservices/microservice-payments/src/db/migrations.ts +18 -0
- package/microservices/microservice-payments/src/db/payments.ts +552 -0
- package/microservices/microservice-payments/src/mcp/index.ts +223 -0
- package/microservices/microservice-payroll/src/cli/index.ts +269 -0
- package/microservices/microservice-payroll/src/db/migrations.ts +26 -0
- package/microservices/microservice-payroll/src/db/payroll.ts +636 -0
- package/microservices/microservice-payroll/src/mcp/index.ts +246 -0
- package/microservices/microservice-shipping/src/cli/index.ts +211 -3
- package/microservices/microservice-shipping/src/db/migrations.ts +8 -0
- package/microservices/microservice-shipping/src/db/shipping.ts +453 -3
- package/microservices/microservice-shipping/src/mcp/index.ts +149 -1
- package/microservices/microservice-social/src/cli/index.ts +244 -2
- package/microservices/microservice-social/src/db/migrations.ts +33 -0
- package/microservices/microservice-social/src/db/social.ts +378 -4
- package/microservices/microservice-social/src/mcp/index.ts +221 -1
- package/microservices/microservice-subscriptions/src/cli/index.ts +315 -0
- package/microservices/microservice-subscriptions/src/db/migrations.ts +68 -0
- package/microservices/microservice-subscriptions/src/db/subscriptions.ts +567 -3
- package/microservices/microservice-subscriptions/src/mcp/index.ts +267 -1
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { getDatabase } from "./database.js";
|
|
|
7
7
|
// --- Contract types ---
|
|
8
8
|
|
|
9
9
|
export type ContractType = "nda" | "service" | "employment" | "license" | "other";
|
|
10
|
-
export type ContractStatus = "draft" | "pending_signature" | "active" | "expired" | "terminated";
|
|
10
|
+
export type ContractStatus = "draft" | "pending_review" | "pending_signature" | "active" | "expired" | "terminated";
|
|
11
11
|
|
|
12
12
|
export interface Contract {
|
|
13
13
|
id: string;
|
|
@@ -245,6 +245,9 @@ export function updateContract(
|
|
|
245
245
|
|
|
246
246
|
if (sets.length === 0) return existing;
|
|
247
247
|
|
|
248
|
+
// Save version history before updating
|
|
249
|
+
saveContractVersion(existing);
|
|
250
|
+
|
|
248
251
|
sets.push("updated_at = datetime('now')");
|
|
249
252
|
params.push(id);
|
|
250
253
|
|
|
@@ -494,3 +497,429 @@ export function markReminderSent(id: string): boolean {
|
|
|
494
497
|
.run(id);
|
|
495
498
|
return result.changes > 0;
|
|
496
499
|
}
|
|
500
|
+
|
|
501
|
+
// --- Obligation operations ---
|
|
502
|
+
|
|
503
|
+
export type ObligationStatus = "pending" | "completed" | "overdue";
|
|
504
|
+
|
|
505
|
+
export interface Obligation {
|
|
506
|
+
id: string;
|
|
507
|
+
clause_id: string;
|
|
508
|
+
description: string;
|
|
509
|
+
due_date: string | null;
|
|
510
|
+
status: ObligationStatus;
|
|
511
|
+
assigned_to: string | null;
|
|
512
|
+
created_at: string;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export interface CreateObligationInput {
|
|
516
|
+
clause_id: string;
|
|
517
|
+
description: string;
|
|
518
|
+
due_date?: string;
|
|
519
|
+
assigned_to?: string;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
export function createObligation(input: CreateObligationInput): Obligation {
|
|
523
|
+
const db = getDatabase();
|
|
524
|
+
const id = crypto.randomUUID();
|
|
525
|
+
|
|
526
|
+
db.prepare(
|
|
527
|
+
`INSERT INTO obligations (id, clause_id, description, due_date, assigned_to)
|
|
528
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
529
|
+
).run(id, input.clause_id, input.description, input.due_date || null, input.assigned_to || null);
|
|
530
|
+
|
|
531
|
+
return getObligation(id)!;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function getObligation(id: string): Obligation | null {
|
|
535
|
+
const db = getDatabase();
|
|
536
|
+
const row = db.prepare("SELECT * FROM obligations WHERE id = ?").get(id) as Obligation | null;
|
|
537
|
+
return row || null;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export function listObligations(clauseId: string): Obligation[] {
|
|
541
|
+
const db = getDatabase();
|
|
542
|
+
return db
|
|
543
|
+
.prepare("SELECT * FROM obligations WHERE clause_id = ? ORDER BY created_at ASC")
|
|
544
|
+
.all(clauseId) as Obligation[];
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export function completeObligation(id: string): Obligation | null {
|
|
548
|
+
const db = getDatabase();
|
|
549
|
+
const result = db
|
|
550
|
+
.prepare("UPDATE obligations SET status = 'completed' WHERE id = ?")
|
|
551
|
+
.run(id);
|
|
552
|
+
if (result.changes === 0) return null;
|
|
553
|
+
return getObligation(id);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export function listOverdueObligations(): Obligation[] {
|
|
557
|
+
const db = getDatabase();
|
|
558
|
+
// Mark obligations past due_date as overdue, then return them
|
|
559
|
+
db.prepare(
|
|
560
|
+
`UPDATE obligations SET status = 'overdue'
|
|
561
|
+
WHERE status = 'pending' AND due_date IS NOT NULL AND date(due_date) < date('now')`
|
|
562
|
+
).run();
|
|
563
|
+
|
|
564
|
+
return db
|
|
565
|
+
.prepare(
|
|
566
|
+
`SELECT * FROM obligations
|
|
567
|
+
WHERE status = 'overdue'
|
|
568
|
+
ORDER BY due_date ASC`
|
|
569
|
+
)
|
|
570
|
+
.all() as Obligation[];
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// --- Approval workflow ---
|
|
574
|
+
|
|
575
|
+
const APPROVAL_FLOW: Record<string, string> = {
|
|
576
|
+
draft: "pending_review",
|
|
577
|
+
pending_review: "pending_signature",
|
|
578
|
+
pending_signature: "active",
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Submit a draft contract for review: draft -> pending_review
|
|
583
|
+
*/
|
|
584
|
+
export function submitForReview(id: string): Contract | null {
|
|
585
|
+
const contract = getContract(id);
|
|
586
|
+
if (!contract) return null;
|
|
587
|
+
if (contract.status !== "draft") {
|
|
588
|
+
throw new Error(`Cannot submit for review: contract status is '${contract.status}', expected 'draft'`);
|
|
589
|
+
}
|
|
590
|
+
return updateContract(id, { status: "pending_review" });
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Approve a contract: pending_review -> pending_signature -> active
|
|
595
|
+
* Advances the contract one step in the approval flow.
|
|
596
|
+
*/
|
|
597
|
+
export function approveContract(id: string): Contract | null {
|
|
598
|
+
const contract = getContract(id);
|
|
599
|
+
if (!contract) return null;
|
|
600
|
+
const nextStatus = APPROVAL_FLOW[contract.status];
|
|
601
|
+
if (!nextStatus) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
`Cannot approve: contract status is '${contract.status}'. Approval flow: draft -> pending_review -> pending_signature -> active`
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
return updateContract(id, { status: nextStatus as ContractStatus });
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// --- Version history ---
|
|
610
|
+
|
|
611
|
+
export interface ContractVersion {
|
|
612
|
+
id: string;
|
|
613
|
+
contract_id: string;
|
|
614
|
+
title: string;
|
|
615
|
+
status: string;
|
|
616
|
+
value: number | null;
|
|
617
|
+
metadata_snapshot: Record<string, unknown>;
|
|
618
|
+
changed_at: string;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
interface ContractVersionRow {
|
|
622
|
+
id: string;
|
|
623
|
+
contract_id: string;
|
|
624
|
+
title: string;
|
|
625
|
+
status: string;
|
|
626
|
+
value: number | null;
|
|
627
|
+
metadata_snapshot: string;
|
|
628
|
+
changed_at: string;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function rowToVersion(row: ContractVersionRow): ContractVersion {
|
|
632
|
+
return {
|
|
633
|
+
...row,
|
|
634
|
+
metadata_snapshot: JSON.parse(row.metadata_snapshot || "{}"),
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function saveContractVersion(contract: Contract): void {
|
|
639
|
+
const db = getDatabase();
|
|
640
|
+
const id = crypto.randomUUID();
|
|
641
|
+
db.prepare(
|
|
642
|
+
`INSERT INTO contract_versions (id, contract_id, title, status, value, metadata_snapshot)
|
|
643
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
644
|
+
).run(
|
|
645
|
+
id,
|
|
646
|
+
contract.id,
|
|
647
|
+
contract.title,
|
|
648
|
+
contract.status,
|
|
649
|
+
contract.value,
|
|
650
|
+
JSON.stringify(contract.metadata)
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export function getContractHistory(contractId: string): ContractVersion[] {
|
|
655
|
+
const db = getDatabase();
|
|
656
|
+
const rows = db
|
|
657
|
+
.prepare("SELECT * FROM contract_versions WHERE contract_id = ? ORDER BY changed_at ASC")
|
|
658
|
+
.all(contractId) as ContractVersionRow[];
|
|
659
|
+
return rows.map(rowToVersion);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// --- Signature logging ---
|
|
663
|
+
|
|
664
|
+
export type SignatureMethod = "digital" | "wet" | "docusign";
|
|
665
|
+
|
|
666
|
+
export interface Signature {
|
|
667
|
+
id: string;
|
|
668
|
+
contract_id: string;
|
|
669
|
+
signer_name: string;
|
|
670
|
+
signer_email: string | null;
|
|
671
|
+
signed_at: string;
|
|
672
|
+
method: SignatureMethod;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
export interface RecordSignatureInput {
|
|
676
|
+
contract_id: string;
|
|
677
|
+
signer_name: string;
|
|
678
|
+
signer_email?: string;
|
|
679
|
+
method?: SignatureMethod;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
export function recordSignature(input: RecordSignatureInput): Signature {
|
|
683
|
+
const db = getDatabase();
|
|
684
|
+
const id = crypto.randomUUID();
|
|
685
|
+
|
|
686
|
+
db.prepare(
|
|
687
|
+
`INSERT INTO signatures (id, contract_id, signer_name, signer_email, method)
|
|
688
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
689
|
+
).run(id, input.contract_id, input.signer_name, input.signer_email || null, input.method || "digital");
|
|
690
|
+
|
|
691
|
+
return getSignature(id)!;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export function getSignature(id: string): Signature | null {
|
|
695
|
+
const db = getDatabase();
|
|
696
|
+
const row = db.prepare("SELECT * FROM signatures WHERE id = ?").get(id) as Signature | null;
|
|
697
|
+
return row || null;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
export function listSignatures(contractId: string): Signature[] {
|
|
701
|
+
const db = getDatabase();
|
|
702
|
+
return db
|
|
703
|
+
.prepare("SELECT * FROM signatures WHERE contract_id = ? ORDER BY signed_at ASC")
|
|
704
|
+
.all(contractId) as Signature[];
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// --- Clause templates (library) ---
|
|
708
|
+
|
|
709
|
+
export interface ClauseTemplate {
|
|
710
|
+
id: string;
|
|
711
|
+
name: string;
|
|
712
|
+
text: string;
|
|
713
|
+
type: "standard" | "custom" | "negotiated";
|
|
714
|
+
created_at: string;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export interface SaveClauseTemplateInput {
|
|
718
|
+
name: string;
|
|
719
|
+
text: string;
|
|
720
|
+
type?: "standard" | "custom" | "negotiated";
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
export function saveClauseTemplate(input: SaveClauseTemplateInput): ClauseTemplate {
|
|
724
|
+
const db = getDatabase();
|
|
725
|
+
const id = crypto.randomUUID();
|
|
726
|
+
|
|
727
|
+
db.prepare(
|
|
728
|
+
`INSERT INTO clause_templates (id, name, text, type)
|
|
729
|
+
VALUES (?, ?, ?, ?)`
|
|
730
|
+
).run(id, input.name, input.text, input.type || "standard");
|
|
731
|
+
|
|
732
|
+
return getClauseTemplate(id)!;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
export function getClauseTemplate(id: string): ClauseTemplate | null {
|
|
736
|
+
const db = getDatabase();
|
|
737
|
+
const row = db.prepare("SELECT * FROM clause_templates WHERE id = ?").get(id) as ClauseTemplate | null;
|
|
738
|
+
return row || null;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
export function getClauseTemplateByName(name: string): ClauseTemplate | null {
|
|
742
|
+
const db = getDatabase();
|
|
743
|
+
const row = db.prepare("SELECT * FROM clause_templates WHERE name = ?").get(name) as ClauseTemplate | null;
|
|
744
|
+
return row || null;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
export function listClauseTemplates(): ClauseTemplate[] {
|
|
748
|
+
const db = getDatabase();
|
|
749
|
+
return db
|
|
750
|
+
.prepare("SELECT * FROM clause_templates ORDER BY name ASC")
|
|
751
|
+
.all() as ClauseTemplate[];
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export function addClauseFromTemplate(contractId: string, templateName: string): Clause {
|
|
755
|
+
const template = getClauseTemplateByName(templateName);
|
|
756
|
+
if (!template) {
|
|
757
|
+
throw new Error(`Clause template '${templateName}' not found`);
|
|
758
|
+
}
|
|
759
|
+
return createClause({
|
|
760
|
+
contract_id: contractId,
|
|
761
|
+
name: template.name,
|
|
762
|
+
text: template.text,
|
|
763
|
+
type: template.type,
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// --- Multi-stage reminders ---
|
|
768
|
+
|
|
769
|
+
export function setMultiReminders(
|
|
770
|
+
contractId: string,
|
|
771
|
+
daysBefore: number[]
|
|
772
|
+
): Reminder[] {
|
|
773
|
+
const contract = getContract(contractId);
|
|
774
|
+
if (!contract) {
|
|
775
|
+
throw new Error(`Contract '${contractId}' not found`);
|
|
776
|
+
}
|
|
777
|
+
if (!contract.end_date) {
|
|
778
|
+
throw new Error(`Contract '${contractId}' has no end_date set`);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const endDate = new Date(contract.end_date);
|
|
782
|
+
const reminders: Reminder[] = [];
|
|
783
|
+
|
|
784
|
+
for (const days of daysBefore) {
|
|
785
|
+
const remindDate = new Date(endDate);
|
|
786
|
+
remindDate.setDate(remindDate.getDate() - days);
|
|
787
|
+
const remindAt = remindDate.toISOString().split("T")[0] + "T09:00:00";
|
|
788
|
+
|
|
789
|
+
const reminder = createReminder({
|
|
790
|
+
contract_id: contractId,
|
|
791
|
+
remind_at: remindAt,
|
|
792
|
+
message: `Contract "${contract.title}" expires in ${days} day(s)`,
|
|
793
|
+
});
|
|
794
|
+
reminders.push(reminder);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
return reminders;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// --- Contract comparison ---
|
|
801
|
+
|
|
802
|
+
export interface ContractComparison {
|
|
803
|
+
contract1: { id: string; title: string };
|
|
804
|
+
contract2: { id: string; title: string };
|
|
805
|
+
field_differences: { field: string; contract1_value: unknown; contract2_value: unknown }[];
|
|
806
|
+
clause_only_in_1: Clause[];
|
|
807
|
+
clause_only_in_2: Clause[];
|
|
808
|
+
clause_differences: { name: string; contract1_text: string; contract2_text: string }[];
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
export function compareContracts(id1: string, id2: string): ContractComparison {
|
|
812
|
+
const c1 = getContract(id1);
|
|
813
|
+
const c2 = getContract(id2);
|
|
814
|
+
if (!c1) throw new Error(`Contract '${id1}' not found`);
|
|
815
|
+
if (!c2) throw new Error(`Contract '${id2}' not found`);
|
|
816
|
+
|
|
817
|
+
// Compare fields
|
|
818
|
+
const fieldsToCompare: (keyof Contract)[] = [
|
|
819
|
+
"title", "type", "status", "counterparty", "counterparty_email",
|
|
820
|
+
"start_date", "end_date", "auto_renew", "renewal_period",
|
|
821
|
+
"value", "currency",
|
|
822
|
+
];
|
|
823
|
+
const field_differences: { field: string; contract1_value: unknown; contract2_value: unknown }[] = [];
|
|
824
|
+
for (const field of fieldsToCompare) {
|
|
825
|
+
const v1 = c1[field];
|
|
826
|
+
const v2 = c2[field];
|
|
827
|
+
if (JSON.stringify(v1) !== JSON.stringify(v2)) {
|
|
828
|
+
field_differences.push({ field, contract1_value: v1, contract2_value: v2 });
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Compare clauses by name
|
|
833
|
+
const clauses1 = listClauses(id1);
|
|
834
|
+
const clauses2 = listClauses(id2);
|
|
835
|
+
const names1 = new Set(clauses1.map((c) => c.name));
|
|
836
|
+
const names2 = new Set(clauses2.map((c) => c.name));
|
|
837
|
+
|
|
838
|
+
const clause_only_in_1 = clauses1.filter((c) => !names2.has(c.name));
|
|
839
|
+
const clause_only_in_2 = clauses2.filter((c) => !names1.has(c.name));
|
|
840
|
+
|
|
841
|
+
const clause_differences: { name: string; contract1_text: string; contract2_text: string }[] = [];
|
|
842
|
+
for (const cl1 of clauses1) {
|
|
843
|
+
const cl2 = clauses2.find((c) => c.name === cl1.name);
|
|
844
|
+
if (cl2 && cl1.text !== cl2.text) {
|
|
845
|
+
clause_differences.push({ name: cl1.name, contract1_text: cl1.text, contract2_text: cl2.text });
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return {
|
|
850
|
+
contract1: { id: c1.id, title: c1.title },
|
|
851
|
+
contract2: { id: c2.id, title: c2.title },
|
|
852
|
+
field_differences,
|
|
853
|
+
clause_only_in_1,
|
|
854
|
+
clause_only_in_2,
|
|
855
|
+
clause_differences,
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// --- Markdown export ---
|
|
860
|
+
|
|
861
|
+
export function exportContract(id: string, format: "md" | "json" = "md"): string {
|
|
862
|
+
const contract = getContract(id);
|
|
863
|
+
if (!contract) throw new Error(`Contract '${id}' not found`);
|
|
864
|
+
|
|
865
|
+
if (format === "json") {
|
|
866
|
+
const clauses = listClauses(id);
|
|
867
|
+
const sigs = listSignatures(id);
|
|
868
|
+
const reminders = listReminders(id);
|
|
869
|
+
return JSON.stringify({ contract, clauses, signatures: sigs, reminders }, null, 2);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Markdown export
|
|
873
|
+
const lines: string[] = [];
|
|
874
|
+
lines.push(`# ${contract.title}`);
|
|
875
|
+
lines.push("");
|
|
876
|
+
lines.push(`| Field | Value |`);
|
|
877
|
+
lines.push(`|-------|-------|`);
|
|
878
|
+
lines.push(`| Type | ${contract.type} |`);
|
|
879
|
+
lines.push(`| Status | ${contract.status} |`);
|
|
880
|
+
if (contract.counterparty) lines.push(`| Counterparty | ${contract.counterparty} |`);
|
|
881
|
+
if (contract.counterparty_email) lines.push(`| Email | ${contract.counterparty_email} |`);
|
|
882
|
+
if (contract.start_date) lines.push(`| Start Date | ${contract.start_date} |`);
|
|
883
|
+
if (contract.end_date) lines.push(`| End Date | ${contract.end_date} |`);
|
|
884
|
+
if (contract.value !== null) lines.push(`| Value | ${contract.value} ${contract.currency} |`);
|
|
885
|
+
if (contract.auto_renew) lines.push(`| Auto-Renew | ${contract.renewal_period || "1 year"} |`);
|
|
886
|
+
lines.push(`| Created | ${contract.created_at} |`);
|
|
887
|
+
lines.push(`| Updated | ${contract.updated_at} |`);
|
|
888
|
+
|
|
889
|
+
const clauses = listClauses(id);
|
|
890
|
+
if (clauses.length > 0) {
|
|
891
|
+
lines.push("");
|
|
892
|
+
lines.push("## Clauses");
|
|
893
|
+
lines.push("");
|
|
894
|
+
for (const clause of clauses) {
|
|
895
|
+
lines.push(`### ${clause.name} (${clause.type})`);
|
|
896
|
+
lines.push("");
|
|
897
|
+
lines.push(clause.text);
|
|
898
|
+
lines.push("");
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const sigs = listSignatures(id);
|
|
903
|
+
if (sigs.length > 0) {
|
|
904
|
+
lines.push("## Signatures");
|
|
905
|
+
lines.push("");
|
|
906
|
+
for (const sig of sigs) {
|
|
907
|
+
const email = sig.signer_email ? ` (${sig.signer_email})` : "";
|
|
908
|
+
lines.push(`- **${sig.signer_name}**${email} — ${sig.method} — ${sig.signed_at}`);
|
|
909
|
+
}
|
|
910
|
+
lines.push("");
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const reminders = listReminders(id);
|
|
914
|
+
if (reminders.length > 0) {
|
|
915
|
+
lines.push("## Reminders");
|
|
916
|
+
lines.push("");
|
|
917
|
+
for (const r of reminders) {
|
|
918
|
+
const sent = r.sent ? " (sent)" : "";
|
|
919
|
+
lines.push(`- ${r.remind_at} — ${r.message}${sent}`);
|
|
920
|
+
}
|
|
921
|
+
lines.push("");
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
return lines.join("\n");
|
|
925
|
+
}
|
|
@@ -55,4 +55,87 @@ export const MIGRATIONS: MigrationEntry[] = [
|
|
|
55
55
|
CREATE INDEX IF NOT EXISTS idx_reminders_remind_at ON reminders(remind_at);
|
|
56
56
|
`,
|
|
57
57
|
},
|
|
58
|
+
{
|
|
59
|
+
id: 2,
|
|
60
|
+
name: "qol_features",
|
|
61
|
+
sql: `
|
|
62
|
+
-- Obligation tracking
|
|
63
|
+
CREATE TABLE IF NOT EXISTS obligations (
|
|
64
|
+
id TEXT PRIMARY KEY,
|
|
65
|
+
clause_id TEXT NOT NULL REFERENCES clauses(id) ON DELETE CASCADE,
|
|
66
|
+
description TEXT NOT NULL,
|
|
67
|
+
due_date TEXT,
|
|
68
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'completed', 'overdue')),
|
|
69
|
+
assigned_to TEXT,
|
|
70
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_obligations_clause ON obligations(clause_id);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_obligations_status ON obligations(status);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_obligations_due_date ON obligations(due_date);
|
|
76
|
+
|
|
77
|
+
-- Rebuild contracts table to add pending_review status
|
|
78
|
+
CREATE TABLE contracts_new (
|
|
79
|
+
id TEXT PRIMARY KEY,
|
|
80
|
+
title TEXT NOT NULL,
|
|
81
|
+
type TEXT NOT NULL DEFAULT 'other' CHECK (type IN ('nda', 'service', 'employment', 'license', 'other')),
|
|
82
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'pending_review', 'pending_signature', 'active', 'expired', 'terminated')),
|
|
83
|
+
counterparty TEXT,
|
|
84
|
+
counterparty_email TEXT,
|
|
85
|
+
start_date TEXT,
|
|
86
|
+
end_date TEXT,
|
|
87
|
+
auto_renew INTEGER NOT NULL DEFAULT 0,
|
|
88
|
+
renewal_period TEXT,
|
|
89
|
+
value REAL,
|
|
90
|
+
currency TEXT NOT NULL DEFAULT 'USD',
|
|
91
|
+
file_path TEXT,
|
|
92
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
93
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
94
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
INSERT INTO contracts_new SELECT * FROM contracts;
|
|
98
|
+
DROP TABLE contracts;
|
|
99
|
+
ALTER TABLE contracts_new RENAME TO contracts;
|
|
100
|
+
|
|
101
|
+
CREATE INDEX IF NOT EXISTS idx_contracts_status ON contracts(status);
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_contracts_type ON contracts(type);
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_contracts_counterparty ON contracts(counterparty);
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_contracts_end_date ON contracts(end_date);
|
|
105
|
+
|
|
106
|
+
-- Contract versions (history)
|
|
107
|
+
CREATE TABLE IF NOT EXISTS contract_versions (
|
|
108
|
+
id TEXT PRIMARY KEY,
|
|
109
|
+
contract_id TEXT NOT NULL REFERENCES contracts(id) ON DELETE CASCADE,
|
|
110
|
+
title TEXT NOT NULL,
|
|
111
|
+
status TEXT NOT NULL,
|
|
112
|
+
value REAL,
|
|
113
|
+
metadata_snapshot TEXT NOT NULL DEFAULT '{}',
|
|
114
|
+
changed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_contract_versions_contract ON contract_versions(contract_id);
|
|
118
|
+
|
|
119
|
+
-- Signatures
|
|
120
|
+
CREATE TABLE IF NOT EXISTS signatures (
|
|
121
|
+
id TEXT PRIMARY KEY,
|
|
122
|
+
contract_id TEXT NOT NULL REFERENCES contracts(id) ON DELETE CASCADE,
|
|
123
|
+
signer_name TEXT NOT NULL,
|
|
124
|
+
signer_email TEXT,
|
|
125
|
+
signed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
126
|
+
method TEXT NOT NULL DEFAULT 'digital' CHECK (method IN ('digital', 'wet', 'docusign'))
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_signatures_contract ON signatures(contract_id);
|
|
130
|
+
|
|
131
|
+
-- Clause templates (library)
|
|
132
|
+
CREATE TABLE IF NOT EXISTS clause_templates (
|
|
133
|
+
id TEXT PRIMARY KEY,
|
|
134
|
+
name TEXT NOT NULL UNIQUE,
|
|
135
|
+
text TEXT NOT NULL,
|
|
136
|
+
type TEXT NOT NULL DEFAULT 'standard' CHECK (type IN ('standard', 'custom', 'negotiated')),
|
|
137
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
138
|
+
);
|
|
139
|
+
`,
|
|
140
|
+
},
|
|
58
141
|
];
|