turash/bugulma/backend/internal/domain/organization.go
Damir Mukimov 000eab4740
Major repository reorganization and missing backend endpoints implementation
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools

Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
  * GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
  * GET /api/v1/users/me/organizations - User organizations
  * POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue

API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules

Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
2025-11-25 06:01:16 +01:00

244 lines
10 KiB
Go

package domain
import (
"context"
"encoding/json"
"time"
"gorm.io/datatypes"
)
// unmarshalStringSlice safely unmarshals datatypes.JSON to []string
func unmarshalStringSlice(data datatypes.JSON) []string {
if len(data) == 0 {
return []string{}
}
var result []string
if err := json.Unmarshal(data, &result); err != nil {
// If it's not an array, return empty slice
return []string{}
}
return result
}
// MarshalJSON custom marshaling to ensure arrays are always arrays
func (o Organization) MarshalJSON() ([]byte, error) {
type Alias Organization
return json.Marshal(&struct {
GalleryImages []string `json:"GalleryImages"`
Certifications []string `json:"Certifications"`
BusinessFocus []string `json:"BusinessFocus"`
TechnicalExpertise []string `json:"TechnicalExpertise"`
AvailableTechnology []string `json:"AvailableTechnology"`
ManagementSystems []string `json:"ManagementSystems"`
TrustNetwork []string `json:"TrustNetwork"`
ExistingSymbioticRelationships []string `json:"ExistingSymbioticRelationships"`
*Alias
}{
GalleryImages: unmarshalStringSlice(o.GalleryImages),
Certifications: unmarshalStringSlice(o.Certifications),
BusinessFocus: unmarshalStringSlice(o.BusinessFocus),
TechnicalExpertise: unmarshalStringSlice(o.TechnicalExpertise),
AvailableTechnology: unmarshalStringSlice(o.AvailableTechnology),
ManagementSystems: unmarshalStringSlice(o.ManagementSystems),
TrustNetwork: unmarshalStringSlice(o.TrustNetwork),
ExistingSymbioticRelationships: unmarshalStringSlice(o.ExistingSymbioticRelationships),
Alias: (*Alias)(&o),
})
}
// OrganizationSubtype defines the category of organization
type OrganizationSubtype string
const (
SubtypeCommercial OrganizationSubtype = "commercial"
SubtypeCultural OrganizationSubtype = "cultural"
SubtypeGovernment OrganizationSubtype = "government"
SubtypeReligious OrganizationSubtype = "religious"
SubtypeEducational OrganizationSubtype = "educational"
SubtypeInfrastructure OrganizationSubtype = "infrastructure"
SubtypeHealthcare OrganizationSubtype = "healthcare"
SubtypeOther OrganizationSubtype = "other"
)
// SectorStat represents sector statistics
type SectorStat struct {
Sector string `json:"sector"`
Count int `json:"count"`
}
// LegalForm represents the legal structure of a business organization
type LegalForm string
const (
LegalFormLLC LegalForm = "LLC"
LegalFormCorporation LegalForm = "corporation"
LegalFormPartnership LegalForm = "partnership"
LegalFormSoleProprietorship LegalForm = "sole_proprietorship"
LegalFormGmbH LegalForm = "GmbH"
LegalFormUG LegalForm = "UG"
LegalFormAG LegalForm = "AG"
)
// SupplyChainRole represents the organization's role in the supply chain
type SupplyChainRole string
const (
RoleManufacturer SupplyChainRole = "manufacturer"
RoleSupplier SupplyChainRole = "supplier"
RoleDistributor SupplyChainRole = "distributor"
RoleConsumer SupplyChainRole = "consumer"
)
// PrimaryContact stores contact information
type PrimaryContact struct {
Email string `json:"email"`
Phone string `json:"phone"`
}
// ProductJSON represents a product offered by an organization (for API/JSON serialization)
type ProductJSON struct {
Name string `json:"name"`
Category string `json:"category"`
UnitPrice float64 `json:"unit_price"`
MOQ int `json:"moq,omitempty"`
Certifications []string `json:"certifications,omitempty"`
}
// ServiceJSON represents a service offered by an organization (for API/JSON serialization)
type ServiceJSON struct {
Type string `json:"type"` // maintenance, consulting, transport, inspection
Domain string `json:"domain"` // compressors, HVAC, waste_management, etc.
OnSite bool `json:"on_site"` // on-site or remote
HourlyRate float64 `json:"hourly_rate"` // €/hour
ServiceAreaKm float64 `json:"service_area_km"` // service radius
Certifications []string `json:"certifications,omitempty"`
}
// ServiceNeedJSON represents a service needed by an organization (for API/JSON serialization)
type ServiceNeedJSON struct {
Type string `json:"type"`
Urgency string `json:"urgency,omitempty"` // low, medium, high
Budget float64 `json:"budget,omitempty"`
PreferredProviders []string `json:"preferred_providers,omitempty"` // Organization IDs
}
type Organization struct {
ID string `gorm:"primaryKey;type:text" json:"ID"`
Name string `gorm:"not null;type:text;index" json:"Name"` // Primary name (Russian)
// Subtype categorization (commercial, healthcare, educational, etc.)
Subtype OrganizationSubtype `gorm:"not null;type:varchar(50);index" json:"Subtype"`
Sector string `gorm:"type:text;index" json:"Sector"` // NACE code or category
// Basic information
Description string `gorm:"type:text" json:"Description"`
LogoURL string `gorm:"column:logo_url;type:text" json:"LogoURL"`
GalleryImages datatypes.JSON `gorm:"column:gallery_images;type:jsonb;default:'[]'" json:"-"` // []string - URLs of gallery images
Website string `gorm:"type:text" json:"Website"`
// Geolocation (from primary address)
Latitude float64 `gorm:"type:double precision;index:idx_org_location" json:"Latitude"`
Longitude float64 `gorm:"type:double precision;index:idx_org_location" json:"Longitude"`
// Business-specific fields (for commercial subtype)
LegalForm string `gorm:"type:varchar(50)" json:"LegalForm"`
PrimaryContact datatypes.JSON `gorm:"type:jsonb" json:"PrimaryContact"` // PrimaryContact
IndustrialSector string `gorm:"type:varchar(10)" json:"IndustrialSector"`
CompanySize int `gorm:"type:integer" json:"CompanySize"`
YearsOperation int `gorm:"type:integer" json:"YearsOperation"`
SupplyChainRole SupplyChainRole `gorm:"type:varchar(50)" json:"SupplyChainRole"`
Certifications datatypes.JSON `gorm:"default:'[]'" json:"-"` // []string
BusinessFocus datatypes.JSON `gorm:"default:'[]'" json:"-"` // []string
StrategicVision string `gorm:"type:text" json:"StrategicVision"`
DriversBarriers string `gorm:"type:text" json:"DriversBarriers"`
ReadinessMaturity int `gorm:"type:integer;default:3" json:"ReadinessMaturity"`
TrustScore float64 `gorm:"type:double precision;default:0.7" json:"TrustScore"`
TechnicalExpertise datatypes.JSON `gorm:"default:'[]'" json:"-"` // []string
AvailableTechnology datatypes.JSON `gorm:"default:'[]'" json:"-"` // []string
ManagementSystems datatypes.JSON `gorm:"default:'[]'" json:"-"` // []string
// Products and Services
SellsProducts datatypes.JSON `gorm:"default:'[]'" json:"SellsProducts"` // []Product
OffersServices datatypes.JSON `gorm:"default:'[]'" json:"OffersServices"` // []Service
NeedsServices datatypes.JSON `gorm:"default:'[]'" json:"NeedsServices"` // []ServiceNeed
// Historical/cultural building fields (for non-commercial subtypes)
YearBuilt string `gorm:"type:text" json:"YearBuilt"`
BuilderOwner string `gorm:"type:text" json:"BuilderOwner"`
Architect string `gorm:"type:text" json:"Architect"`
OriginalPurpose string `gorm:"type:text" json:"OriginalPurpose"`
CurrentUse string `gorm:"type:text" json:"CurrentUse"`
Style string `gorm:"type:text" json:"Style"`
Materials string `gorm:"type:text" json:"Materials"`
Storeys int `gorm:"type:integer" json:"Storeys"`
HeritageStatus string `gorm:"type:text" json:"HeritageStatus"`
// Metadata
Verified bool `gorm:"default:false;index" json:"Verified"`
Notes string `gorm:"type:text" json:"Notes"`
Sources datatypes.JSON `gorm:"type:jsonb" json:"Sources"`
// Relationships (will be populated via associations)
TrustNetwork datatypes.JSON `gorm:"type:jsonb;default:'[]'" json:"-"` // []string - Organization IDs
ExistingSymbioticRelationships datatypes.JSON `gorm:"type:jsonb;default:'[]'" json:"-"` // []string - Organization IDs
// Products/materials for legacy compatibility
Products datatypes.JSON `gorm:"type:jsonb" json:"Products"`
// Timestamps
CreatedAt time.Time `gorm:"autoCreateTime;index" json:"CreatedAt"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"UpdatedAt"`
// Associations
Addresses []Address `gorm:"many2many:organization_addresses;" json:"Addresses,omitempty"`
OwnedSites []Site `gorm:"foreignKey:OwnerOrganizationID" json:"OwnedSites,omitempty"`
OperatingSites []Site `gorm:"many2many:site_operating_organizations;" json:"OperatingSites,omitempty"`
ResourceFlows []ResourceFlow `gorm:"foreignKey:OrganizationID" json:"ResourceFlows,omitempty"`
}
// TableName specifies the table name for GORM
func (Organization) TableName() string {
return "organizations"
}
// PrimaryAddress returns the primary (headquarters) address formatted string
func (o *Organization) PrimaryAddress() string {
for _, addr := range o.Addresses {
if addr.AddressType == AddressTypeHeadquarters {
if addr.FormattedRu != "" {
return addr.FormattedRu
}
if addr.FormattedEn != "" {
return addr.FormattedEn
}
}
}
// Fallback to any address if no headquarters found
if len(o.Addresses) > 0 {
if o.Addresses[0].FormattedRu != "" {
return o.Addresses[0].FormattedRu
}
if o.Addresses[0].FormattedEn != "" {
return o.Addresses[0].FormattedEn
}
}
return ""
}
type OrganizationRepository interface {
Create(ctx context.Context, org *Organization) error
GetByID(ctx context.Context, id string) (*Organization, error)
GetAll(ctx context.Context) ([]*Organization, error)
Update(ctx context.Context, org *Organization) error
Delete(ctx context.Context, id string) error
GetBySector(ctx context.Context, sector string) ([]*Organization, error)
GetBySubtype(ctx context.Context, subtype OrganizationSubtype) ([]*Organization, error)
GetWithinRadius(ctx context.Context, lat, lng, radiusKm float64) ([]*Organization, error)
GetByCertification(ctx context.Context, cert string) ([]*Organization, error)
Search(ctx context.Context, query string, limit int) ([]*Organization, error)
SearchSuggestions(ctx context.Context, query string, limit int) ([]string, error)
GetSectorStats(ctx context.Context, limit int) ([]SectorStat, error)
}