2024-11-21

Od Mockupa do Produkcije za 3 Dana: PMFA Workflow Magic

Petak, 9:00 - Sastanak sa klijentom. Traži novi Customer Relationship Management (CRM) modul za B2B sales team.

Requirements:

  • ✅ Kontakti (companies + people)
  • ✅ Deals pipeline (stages: Prospect → Qualified → Proposal → Negotiation → Won/Lost)
  • ✅ Email tracking
  • ✅ Tasks & reminders
  • ✅ Custom fields per industry (Real Estate, Manufacturing, IT Services)
  • ✅ Reporting dashboard

Tradicionalni pristup: "Procena je 3 meseca development + 2 nedelje testiranja."

PMFA pristup: "Mogu imati beta verziju spremnu za ponedeljak ujutro."

Klijent: "Nemoguće."

Evo kako:


Dan 1 (Petak): Mockup + Metadata Definition

09:00-12:00 - Wireframes u Figma

Dizajner kreira basic wireframes:

  • Contacts list page (tabela sa filterima)
  • Contact detail page (info + deal history)
  • Deal pipeline (Kanban board)
  • Task list (todo items sa due dates)

Export: Figma → PDF + annotated screenshots.

13:00-17:00 - YAML Metadata Definition

Backend dev piše YAML entitete. Nema TypeScript koda. Samo metadata.

Contact Entity

# metadata/entities/contact.yml
entity: Contact
table: contacts
icon: "👤"
description: "B2B kontakti (companies & people)"

fields:
  - name: id
    type: uuid
    primary: true
    generated: auto

  - name: type
    type: enum
    values: [company, person]
    default: company
    required: true
    label: "Tip kontakta"

  - name: companyName
    type: string
    maxLength: 200
    required: true
    searchable: true
    label: "Naziv kompanije"

  - name: industry
    type: enum
    values: [real_estate, manufacturing, it_services, finance, healthcare, other]
    required: true
    label: "Industrija"

  - name: website
    type: url
    label: "Website"

  - name: phone
    type: phone
    format: international
    label: "Telefon"

  - name: email
    type: email
    required: true
    unique: true
    label: "Email"

  - name: address
    type: object
    fields:
      - name: street
        type: string
      - name: city
        type: string
      - name: country
        type: string
        default: "Serbia"
      - name: postalCode
        type: string

  - name: assignedTo
    type: relation
    target: User
    required: true
    label: "Assigned Sales Rep"

  - name: deals
    type: relation
    target: Deal
    relation: one-to-many
    backRef: contact

  - name: notes
    type: text
    label: "Internal Notes"

  - name: tags
    type: array
    itemType: string
    label: "Tags"

  - name: customFields
    type: jsonb
    label: "Custom Industry Fields"
    dynamic: true  # Per-tenant custom fields

  - name: createdAt
    type: timestamp
    generated: auto

  - name: updatedAt
    type: timestamp
    generated: auto-update

permissions:
  create: [sales_rep, sales_manager, admin]
  read: [sales_rep, sales_manager, admin]
  update: [assignedTo, sales_manager, admin]
  delete: [admin]

ui:
  listView:
    columns: [companyName, industry, email, assignedTo, createdAt]
    filters: [industry, assignedTo, createdAt]
    sortBy: createdAt
    sortOrder: desc

  detailView:
    layout: two-column
    sections:
      - title: "Basic Info"
        fields: [companyName, type, industry, website]
      - title: "Contact Details"
        fields: [email, phone, address]
      - title: "Assignment"
        fields: [assignedTo, tags]
      - title: "Related"
        fields: [deals]

Trajanje: 30 minuta za jedan entitet.


Deal Entity

# metadata/entities/deal.yml
entity: Deal
table: deals
icon: "💼"
description: "Sales pipeline deals"

fields:
  - name: id
    type: uuid
    primary: true

  - name: title
    type: string
    maxLength: 200
    required: true
    searchable: true

  - name: contact
    type: relation
    target: Contact
    required: true

  - name: stage
    type: enum
    values: [prospect, qualified, proposal, negotiation, won, lost]
    default: prospect
    required: true
    indexed: true

  - name: value
    type: decimal
    precision: 15
    scale: 2
    required: true
    label: "Deal Value (EUR)"

  - name: probability
    type: integer
    min: 0
    max: 100
    default: 10
    label: "Win Probability (%)"
    computed: |
      CASE stage
        WHEN 'prospect' THEN 10
        WHEN 'qualified' THEN 25
        WHEN 'proposal' THEN 50
        WHEN 'negotiation' THEN 75
        WHEN 'won' THEN 100
        WHEN 'lost' THEN 0
      END

  - name: expectedCloseDate
    type: date
    required: true

  - name: actualCloseDate
    type: date
    required: false

  - name: lostReason
    type: string
    maxLength: 500
    required: false
    visibleIf: "stage == 'lost'"

  - name: assignedTo
    type: relation
    target: User
    required: true

  - name: createdAt
    type: timestamp

  - name: updatedAt
    type: timestamp

workflow:
  states:
    - name: prospect
      label: "🔍 Prospect"
      color: gray
      transitions: [qualify, mark_lost]

    - name: qualified
      label: "✅ Qualified"
      color: blue
      transitions: [send_proposal, mark_lost]

    - name: proposal
      label: "📄 Proposal Sent"
      color: purple
      transitions: [start_negotiation, mark_lost]

    - name: negotiation
      label: "💬 Negotiating"
      color: orange
      transitions: [mark_won, mark_lost]

    - name: won
      label: "🎉 Won"
      color: green
      final: true

    - name: lost
      label: "❌ Lost"
      color: red
      final: true

  transitions:
    - name: qualify
      from: prospect
      to: qualified
      action: "Qualify Lead"

    - name: send_proposal
      from: qualified
      to: proposal
      action: "Send Proposal"
      notifications:
        - target: assignedTo
          message: "Proposal sent for deal: {{ title }}"

    - name: start_negotiation
      from: proposal
      to: negotiation
      action: "Start Negotiation"

    - name: mark_won
      from: negotiation
      to: won
      action: "Mark as Won"
      requires_field: actualCloseDate
      notifications:
        - target: assignedTo
          message: "🎉 Congratulations! Deal won: {{ title }}"

    - name: mark_lost
      from: [prospect, qualified, proposal, negotiation]
      to: lost
      action: "Mark as Lost"
      requires_field: lostReason

permissions:
  create: [sales_rep, sales_manager, admin]
  read: [sales_rep, sales_manager, admin]
  update: [assignedTo, sales_manager, admin]
  delete: [admin]

ui:
  listView:
    defaultView: kanban
    kanbanColumn: stage
    columns: [title, contact, value, probability, expectedCloseDate, assignedTo]
    filters: [stage, assignedTo, expectedCloseDate]

  detailView:
    layout: single-column
    sections:
      - title: "Deal Info"
        fields: [title, contact, value, probability]
      - title: "Timeline"
        fields: [stage, expectedCloseDate, actualCloseDate]
      - title: "Assignment"
        fields: [assignedTo]

Trajanje: 45 minuta za kompleksan entitet sa workflow-om.


Task Entity

# metadata/entities/task.yml
entity: Task
table: tasks
icon: "✅"
description: "CRM tasks & reminders"

fields:
  - name: id
    type: uuid
    primary: true

  - name: title
    type: string
    maxLength: 200
    required: true

  - name: description
    type: text

  - name: relatedTo
    type: polymorphic
    targets: [Contact, Deal]
    label: "Related To"

  - name: dueDate
    type: datetime
    required: true

  - name: completed
    type: boolean
    default: false

  - name: priority
    type: enum
    values: [low, medium, high, urgent]
    default: medium

  - name: assignedTo
    type: relation
    target: User
    required: true

  - name: createdAt
    type: timestamp

permissions:
  create: [sales_rep, sales_manager, admin]
  read: [assignedTo, admin]
  update: [assignedTo, admin]
  delete: [assignedTo, admin]

ui:
  listView:
    columns: [title, relatedTo, dueDate, priority, completed, assignedTo]
    filters: [completed, priority, assignedTo, dueDate]
    sortBy: dueDate

Trajanje: 20 minuta za simple entitet.


Total YAML Writing Time: ~2 sata

Definisali smo:

  • 3 entiteta (Contact, Deal, Task)
  • Workflow state machine
  • Validations
  • Permissions
  • UI layout hints

Nema TypeScript koda. Nema database migration-a. Nema REST kontrolera.


Dan 2 (Subota): PMFA Code Generation + Frontend Scaffolding

09:00-10:00 - Generisanje Backend Koda

# PMFA CLI command
pmfa generate --entities=all --output=./backend

Generating entities...
✓ Contact (metadata/entities/contact.yml)
  → backend/src/entities/Contact.ts (TypeScript interface)
  → backend/src/db/migrations/001_create_contacts.sql
  → backend/src/api/controllers/ContactController.ts (REST API)
  → backend/src/api/routes/contacts.ts
  
✓ Deal (metadata/entities/deal.yml)
  → backend/src/entities/Deal.ts
  → backend/src/db/migrations/002_create_deals.sql
  → backend/src/api/controllers/DealController.ts
  → backend/src/workflows/DealWorkflow.ts (state machine)
  
✓ Task (metadata/entities/task.yml)
  → backend/src/entities/Task.ts
  → backend/src/db/migrations/003_create_tasks.sql
  → backend/src/api/controllers/TaskController.ts

Generated:
  - 12 TypeScript files
  - 3 SQL migration files
  - 15 REST API endpoints
  - 1 workflow state machine
  
Total time: 12 seconds

PMFA automatski generiše:

✅ TypeScript interfejsi
✅ PostgreSQL tabele, indeksi, foreign keys
✅ REST API endpoints (GET /contacts, POST /deals, PATCH /deals/:id/qualify, etc.)
✅ Workflow state machine za Deal entity
✅ Permission checks
✅ Validation rules

10:00-12:00 - Database Setup + API Testing

# Pokreni migracije
pmfa migrate --up

Running migrations...
✓ 001_create_contacts.sql
✓ 002_create_deals.sql
✓ 003_create_tasks.sql

# Pokreni dev server
pmfa serve --port=3000

PMFA server running on http://localhost:3000
API endpoints:
  GET    /api/contacts
  POST   /api/contacts
  GET    /api/contacts/:id
  PATCH  /api/contacts/:id
  DELETE /api/contacts/:id
  
  GET    /api/deals
  POST   /api/deals
  POST   /api/deals/:id/qualify
  POST   /api/deals/:id/send_proposal
  POST   /api/deals/:id/mark_won
  ...

API testing sa Postman:

# Kreiranje kontakta
POST http://localhost:3000/api/contacts
{
  "companyName": "Acme Corp",
  "type": "company",
  "industry": "manufacturing",
  "email": "contact@acme.com",
  "assignedTo": "user-uuid-123"
}

# Response: 201 Created
{
  "id": "contact-uuid-456",
  "companyName": "Acme Corp",
  ...
}

✅ Sve radi out-of-the-box.

13:00-17:00 - Frontend UI Scaffolding

PMFA generiše React komponente iz metadata:

pmfa generate:ui --framework=react --output=./frontend

Generating React components...
✓ ContactListView.tsx (tabela sa filterima)
✓ ContactDetailView.tsx (form sa validacijama)
✓ DealKanbanView.tsx (kanban board)
✓ TaskListView.tsx (todo lista)

Generated:
  - 12 React komponenti
  - 8 custom hooks (useContacts, useDeals, useTasks...)
  - Form validation schemas (Zod)
  - API client functions (React Query)

Rezultat:

// frontend/src/pages/ContactsPage.tsx (auto-generated)
import { useContacts } from '../hooks/useContacts';
import { ContactListView } from '../components/ContactListView';

export function ContactsPage() {
  const { data: contacts, isLoading, filters, setFilters } = useContacts();

  return (
    <ContactListView
      contacts={contacts}
      isLoading={isLoading}
      filters={filters}
      onFilterChange={setFilters}
      columns={['companyName', 'industry', 'email', 'assignedTo', 'createdAt']}
    />
  );
}

100% type-safe. Zero ručni kod.


Dan 3 (Nedelja): Custom Logic + Industry-Specific Fields

09:00-12:00 - Custom Fields Per Industry

Klijent traži custom fields:

  • Real Estate: property_type, square_meters, location
  • Manufacturing: annual_production, certifications
  • IT Services: tech_stack, team_size

PMFA pristup:

# metadata/custom-fields/real-estate.yml
entity: Contact
condition: "industry == 'real_estate'"

customFields:
  - name: propertyType
    type: enum
    values: [residential, commercial, industrial, land]
    label: "Property Type"

  - name: squareMeters
    type: integer
    label: "Square Meters"

  - name: location
    type: object
    fields:
      - name: address
        type: string
      - name: latitude
        type: decimal
      - name: longitude
        type: decimal

PMFA dinamički dodaje ova polja u:

  • ✅ Database schema (JSONB kolona ili separate tabele)
  • ✅ TypeScript tipovi
  • ✅ Frontend forme
  • ✅ Validacije

Trajanje: 30 minuta za 3 industrije.

13:00-15:00 - Custom Business Logic

Klijent traži: "Kada deal bude mark as won, automatski kreiraj task za onboarding."

PMFA Hook System:

# metadata/hooks/deal-won-onboarding.yml
entity: Deal
trigger: after_transition
transition: mark_won

action: create_task
params:
  title: "Onboarding: {{ deal.contact.companyName }}"
  description: "Schedule onboarding call for new client"
  relatedTo: "{{ deal.contact.id }}"
  assignedTo: "{{ deal.assignedTo }}"
  dueDate: "{{ now() + 3days }}"
  priority: high

Alternative (TypeScript za kompleksnu logiku):

// backend/src/hooks/dealWonOnboarding.ts
import { Hook } from '@pmfa/core';

export const dealWonOnboarding: Hook = {
  entity: 'Deal',
  trigger: 'after_transition',
  transition: 'mark_won',
  
  async execute(ctx) {
    const { deal } = ctx.data;
    
    await ctx.tasks.create({
      title: `Onboarding: ${deal.contact.companyName}`,
      description: `Schedule onboarding call for new client`,
      relatedTo: deal.contact.id,
      assignedTo: deal.assignedTo,
      dueDate: addDays(new Date(), 3),
      priority: 'high'
    });
    
    // Pošalji Slack notifikaciju
    await ctx.slack.sendMessage({
      channel: '#sales',
      text: `🎉 New deal won: ${deal.title} (${deal.value} EUR)`
    });
  }
};

Trajanje: 1 sat za custom logic.


16:00-17:00 - Testing & Bug Fixes

  • Testiranje UI flow-ova
  • Fixing edge cases
  • Performance tuning (query optimizacija)

Ponedeljak, 09:00 - Demo Sa Klijentom

Rezultat:

✅ Pun CRM modul sa Contacts, Deals, Tasks
✅ Kanban board za deal pipeline
✅ Custom fields per industry
✅ Workflow automation (won deal → onboarding task)
✅ Email notifikacije
✅ Role-based permissions
✅ Mobile-responsive UI

Klijent: "Ovo je gotovo?! Za 3 dana??"

Mi: "Da. Ovo je metadata-driven development."


Traditional vs PMFA: Breakdown

Task Traditional PMFA
Backend Development 6 nedelja 2 sata (YAML metadata)
Database Schema 1 nedelja (migrations) 10 sekundi (auto-generated)
REST API 3 nedelje (controllers, validations) 10 sekundi (auto-generated)
Frontend UI 4 nedelje (React komponente) 3 sata (scaffolding + customizacija)
Workflow Logic 2 nedelje (state machine, notifications) 30 minuta (YAML workflow)
Custom Fields 1 nedelja (database, backend, frontend) 30 minuta (YAML custom fields)
Testing 2 nedelje 1 dan (samo integration testing)
TOTAL 3 meseca 3 dana

Ušteda: 90% vremena.


Conclusion

Metadata-first development = 30x brži delivery.

Umesto da pišemo:

  • TypeScript modele
  • SQL migracije
  • REST kontrolere
  • React forme
  • Validacije
  • Permissions checks

Mi pišemo YAML metadata, a PMFA automatski generiše sve ostalo.

Rezultat:

  • ✅ 3 dana development umesto 3 meseca
  • ✅ Zero tehnički dug
  • ✅ Perfect type safety
  • ✅ Instant izmene (samo edit YAML, regenerate)

PMFA omogućava shipping production-ready software za dane, ne mesece.


Želiš da probaš? Kontaktirajte nas na office@nonnotech.com za early access program.