mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
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)
244 lines
10 KiB
Go
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)
|
|
}
|