|
|
@@ -2,8 +2,9 @@ package repo
|
|
|
|
|
|
import (
|
|
|
"database/sql"
|
|
|
- "fmt"
|
|
|
- "saura/src/common"
|
|
|
+ "math/rand"
|
|
|
+
|
|
|
+ "github.com/google/uuid"
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
@@ -15,277 +16,113 @@ const (
|
|
|
ClientTypeCustomer string = "Покупатель"
|
|
|
)
|
|
|
|
|
|
-type ClientInfo struct {
|
|
|
- Id string `db:"id" json:"id" form:"id"`
|
|
|
- Id2 string `db:"id2" json:"id2" form:"id2"`
|
|
|
- Mark string `db:"mark" json:"mark" form:"mark"`
|
|
|
- Contractor bool `db:"contractor" json:"contractor" form:"contractor"`
|
|
|
- FullName string `db:"full_name" json:"full_name" form:"full_name"`
|
|
|
- Type string `db:"type" json:"type" form:"type"`
|
|
|
- Phones []string `json:"phones" form:"phones"`
|
|
|
- Email string `db:"email" json:"email" form:"email"`
|
|
|
- LegalAddress string `db:"legal_address" json:"legal_address" form:"legal_address"`
|
|
|
- PhysicalAddress string `db:"physical_address" json:"physical_address" form:"physical_address"`
|
|
|
- RegistrationDate string `db:"registration_date" json:"registration_date" form:"registration_date"`
|
|
|
- AdChannel string `db:"ad_channel" json:"ad_channel" form:"ad_channel"`
|
|
|
- RegData1 string `db:"reg_data_1" json:"reg_data_1" form:"reg_data_1"`
|
|
|
- RegData2 string `db:"reg_data_2" json:"reg_data_2" form:"reg_data_2"`
|
|
|
- Note string `db:"note" json:"note" form:"note"`
|
|
|
- RequestCount int `db:"request_count" json:"request_count" form:"request_count"`
|
|
|
- Birthday string `db:"birthday" json:"birthday" form:"birthday"`
|
|
|
- Income common.Money `db:"income" json:"income" form:"income"`
|
|
|
-}
|
|
|
-
|
|
|
-type ClientRepo struct {
|
|
|
- db *sql.DB
|
|
|
- dbUrl string
|
|
|
-}
|
|
|
-
|
|
|
-func NewClientRepo(db *sql.DB, dbUrl string) *ClientRepo {
|
|
|
- return &ClientRepo{db: db, dbUrl: dbUrl}
|
|
|
+type Money struct {
|
|
|
+ Amount int64 `json:"amount"`
|
|
|
+ Currency string `json:"currency"`
|
|
|
}
|
|
|
|
|
|
-func (r *ClientRepo) InsertClient(client ClientInfo) error {
|
|
|
- var err error
|
|
|
- var exists bool
|
|
|
-
|
|
|
- err = r.db.QueryRow("SELECT EXISTS(SELECT 1 FROM clients WHERE id = ? OR id2 = ?)", client.Id, client.Id2).Scan(&exists)
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("failed to check if client exists: %w", err)
|
|
|
- }
|
|
|
- if exists {
|
|
|
- return fmt.Errorf("client with id %s already exists", client.Id)
|
|
|
- }
|
|
|
-
|
|
|
- _, err = r.db.Exec(
|
|
|
- `
|
|
|
- INSERT INTO clients (
|
|
|
- id, id2, mark, contractor, full_name, type, email,
|
|
|
- legal_address, physical_address, registration_date, ad_channel,
|
|
|
- reg_data_1, reg_data_2, note, request_count, birthday, income
|
|
|
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
|
- `,
|
|
|
- client.Id, client.Id2, client.Mark, client.Contractor, client.FullName,
|
|
|
- client.Type, client.Email, client.LegalAddress,
|
|
|
- client.PhysicalAddress, client.RegistrationDate, client.AdChannel,
|
|
|
- client.RegData1, client.RegData2, client.Note, client.RequestCount,
|
|
|
- client.Birthday, client.Income,
|
|
|
- )
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("failed to insert client: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- return nil
|
|
|
+type ClientInfo struct {
|
|
|
+ ID string `db:"id" json:"id"`
|
|
|
+ OldID string `db:"old_id" json:"old_id"`
|
|
|
+ Contractor bool `db:"contractor" json:"contractor"`
|
|
|
+ FullName string `db:"full_name" json:"full_name"`
|
|
|
+ Type string `db:"type" json:"type"`
|
|
|
+ Email string `db:"email" json:"email"`
|
|
|
+ LegalAddress string `db:"legal_address" json:"legal_address"`
|
|
|
+ PhysicalAddress string `db:"physical_address" json:"physical_address"`
|
|
|
+ Note string `db:"note" json:"note"`
|
|
|
+
|
|
|
+ Income Money `json:"income"`
|
|
|
+
|
|
|
+ Tags []string `json:"tags"`
|
|
|
+ Phones []string `json:"phones"`
|
|
|
+ Metadata map[string]string `json:"metadata"`
|
|
|
}
|
|
|
|
|
|
-func (r *ClientRepo) FetchClientByID(id string) (*ClientInfo, error) {
|
|
|
- var err error
|
|
|
-
|
|
|
- row := r.db.QueryRow(`
|
|
|
- SELECT
|
|
|
- id, id2, mark, contractor, full_name, type, email,
|
|
|
- legal_address, physical_address, registration_date, ad_channel,
|
|
|
- reg_data_1, reg_data_2, note, request_count, birthday, income
|
|
|
- FROM clients
|
|
|
- WHERE id = ?
|
|
|
- `, id)
|
|
|
-
|
|
|
- var client ClientInfo
|
|
|
- err = row.Scan(
|
|
|
- &client.Id, &client.Id2, &client.Mark, &client.Contractor, &client.FullName,
|
|
|
- &client.Type, &client.Email, &client.LegalAddress,
|
|
|
- &client.PhysicalAddress, &client.RegistrationDate, &client.AdChannel,
|
|
|
- &client.RegData1, &client.RegData2, &client.Note, &client.RequestCount,
|
|
|
- &client.Birthday, &client.Income,
|
|
|
- )
|
|
|
-
|
|
|
- if err == sql.ErrNoRows {
|
|
|
- return nil, fmt.Errorf("client with id %s not found", id)
|
|
|
- } else if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to scan row: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- phones, err := r.FetchClientPhones(client.Id)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("error: %w", err)
|
|
|
- }
|
|
|
- client.Phones = phones
|
|
|
-
|
|
|
- return &client, nil
|
|
|
+// ClientTag представляет запись из таблицы client_tags
|
|
|
+type ClientTag struct {
|
|
|
+ ClientID string `db:"client_id"`
|
|
|
+ Tag string `db:"tag"`
|
|
|
}
|
|
|
|
|
|
-func (r *ClientRepo) InsertClientPhones(client_id string, phones []string) error {
|
|
|
- for _, phone := range phones {
|
|
|
- _, err := r.db.Exec("INSERT INTO client_phones VALUES(?, ?)", client_id, phone)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- }
|
|
|
- return nil
|
|
|
+// ClientPhone представляет запись из таблицы client_phones
|
|
|
+type ClientPhone struct {
|
|
|
+ ClientID string `db:"client_id"`
|
|
|
+ Phone string `db:"phone"`
|
|
|
}
|
|
|
|
|
|
-func (r *ClientRepo) FetchClientPhones(client_id string) ([]string, error) {
|
|
|
- rows, err := r.db.Query("SELECT phone FROM client_phones WHERE client_id = ?", client_id)
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- defer rows.Close()
|
|
|
-
|
|
|
- var tags []string
|
|
|
- for rows.Next() {
|
|
|
- var tag string
|
|
|
- rows.Scan(&tag)
|
|
|
- tags = append(tags, tag)
|
|
|
- }
|
|
|
- return tags, nil
|
|
|
+// ClientMetadata представляет запись из таблицы client_metadata
|
|
|
+type ClientMetadata struct {
|
|
|
+ ClientID string `db:"client_id"`
|
|
|
+ Key string `db:"key"`
|
|
|
+ Value string `db:"value"`
|
|
|
}
|
|
|
|
|
|
-func (r *ClientRepo) FetchClients(count int, offset int) ([]ClientInfo, error) {
|
|
|
- rows, err := r.db.Query(`
|
|
|
- SELECT
|
|
|
- id, id2, mark, contractor, full_name, type, email,
|
|
|
- legal_address, physical_address, registration_date, ad_channel,
|
|
|
- reg_data_1, reg_data_2, note, request_count, birthday, income
|
|
|
- FROM clients
|
|
|
- LIMIT ? OFFSET ?
|
|
|
- `, count, offset)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to execute query: %w", err)
|
|
|
- }
|
|
|
- defer rows.Close()
|
|
|
-
|
|
|
- var clients []ClientInfo
|
|
|
- for rows.Next() {
|
|
|
- var client ClientInfo
|
|
|
- err := rows.Scan(
|
|
|
- &client.Id,
|
|
|
- &client.Id2,
|
|
|
- &client.Mark,
|
|
|
- &client.Contractor,
|
|
|
- &client.FullName,
|
|
|
- &client.Type,
|
|
|
- &client.Email,
|
|
|
- &client.LegalAddress,
|
|
|
- &client.PhysicalAddress,
|
|
|
- &client.RegistrationDate, // time.Time или sql.NullTime
|
|
|
- &client.AdChannel,
|
|
|
- &client.RegData1,
|
|
|
- &client.RegData2,
|
|
|
- &client.Note, // sql.NullString
|
|
|
- &client.RequestCount, // int или sql.NullInt32
|
|
|
- &client.Birthday, // time.Time или sql.NullTime
|
|
|
- &client.Income, // float64 или sql.NullFloat64
|
|
|
- )
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to scan row: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- phones, err := r.FetchClientPhones(client.Id)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("rows error: %w", err)
|
|
|
- }
|
|
|
- client.Phones = phones
|
|
|
-
|
|
|
- clients = append(clients, client)
|
|
|
- }
|
|
|
-
|
|
|
- // Проверка ошибок после итерации
|
|
|
- if err := rows.Err(); err != nil {
|
|
|
- return nil, fmt.Errorf("rows error: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- return clients, nil
|
|
|
+type ClientRepo struct {
|
|
|
+ db *sql.DB
|
|
|
+ dbUrl string
|
|
|
}
|
|
|
|
|
|
-func (r *ClientRepo) FetchClientsCount() (int, error) {
|
|
|
- var count int
|
|
|
- query := "SELECT COUNT(*) FROM clients"
|
|
|
- err := r.db.QueryRow(query).Scan(&count)
|
|
|
- if err != nil {
|
|
|
- return 0, fmt.Errorf("failed to fetch count: %w", err)
|
|
|
- }
|
|
|
- return count, nil
|
|
|
+func NewClientRepo(db *sql.DB, dbUrl string) *ClientRepo {
|
|
|
+ return &ClientRepo{db: db, dbUrl: dbUrl}
|
|
|
}
|
|
|
|
|
|
-func (r *ClientRepo) DeleteClient(id string) error {
|
|
|
- query := "DELETE FROM clients WHERE id = ?"
|
|
|
- result, err := r.db.Exec(query, id)
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("failed to delete client: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- rowsAffected, err := result.RowsAffected()
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("failed to get rows affected: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- if rowsAffected == 0 {
|
|
|
- return fmt.Errorf("client with id %s not found", id)
|
|
|
- }
|
|
|
-
|
|
|
- return nil
|
|
|
+func (r *ClientRepo) FetchClients(limit int, offset int) ([]ClientInfo, error) {
|
|
|
+ var clients = []ClientInfo{}
|
|
|
+ return clients[offset:limit], nil
|
|
|
}
|
|
|
|
|
|
-func (r *ClientRepo) UpdateClient(id string, client ClientInfo) error {
|
|
|
- // Проверим, существует ли клиент
|
|
|
- var exists string
|
|
|
- err := r.db.QueryRow("SELECT id FROM clients WHERE id = ?", id).Scan(&exists)
|
|
|
- if err == sql.ErrNoRows {
|
|
|
- return fmt.Errorf("client with id %s not found", id)
|
|
|
- } else if err != nil {
|
|
|
- return fmt.Errorf("failed to check client existence: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- query := `
|
|
|
- UPDATE clients SET
|
|
|
- id = ?,
|
|
|
- id2 = ?,
|
|
|
- mark = ?,
|
|
|
- contractor = ?,
|
|
|
- full_name = ?,
|
|
|
- type = ?,
|
|
|
- email = ?,
|
|
|
- legal_address = ?,
|
|
|
- physical_address = ?,
|
|
|
- registration_date = ?,
|
|
|
- ad_channel = ?,
|
|
|
- reg_data_1 = ?,
|
|
|
- reg_data_2 = ?,
|
|
|
- note = ?,
|
|
|
- request_count = ?,
|
|
|
- birthday = ?,
|
|
|
- income = ?
|
|
|
- WHERE id = ?
|
|
|
- `
|
|
|
-
|
|
|
- contractorInt := 0
|
|
|
- if client.Contractor {
|
|
|
- contractorInt = 1
|
|
|
- }
|
|
|
-
|
|
|
- _, err = r.db.Exec(query,
|
|
|
- id,
|
|
|
- client.Id2,
|
|
|
- client.Mark,
|
|
|
- contractorInt,
|
|
|
- client.FullName,
|
|
|
- client.Type,
|
|
|
- client.Email,
|
|
|
- client.LegalAddress,
|
|
|
- client.PhysicalAddress,
|
|
|
- client.RegistrationDate,
|
|
|
- client.AdChannel,
|
|
|
- client.RegData1,
|
|
|
- client.RegData2,
|
|
|
- client.Note,
|
|
|
- client.RequestCount,
|
|
|
- client.Birthday,
|
|
|
- client.Income,
|
|
|
- id,
|
|
|
- )
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("failed to update client: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- return nil
|
|
|
+func (r *ClientRepo) FetchExampleClients(limit int, offset int) []ClientInfo {
|
|
|
+ rand.Seed(42)
|
|
|
+
|
|
|
+ clients := []ClientInfo{
|
|
|
+ {
|
|
|
+ ID: uuid.New().String(),
|
|
|
+ FullName: "Иванов Иван Иванович",
|
|
|
+ Type: ClientTypePhysical,
|
|
|
+ Contractor: false,
|
|
|
+ Email: "ivanov@example.com",
|
|
|
+ PhysicalAddress: "г. Москва, ул. Тверская, д. 1, кв. 42",
|
|
|
+ Note: "Постоянный клиент",
|
|
|
+ Income: Money{19999, "RUB"},
|
|
|
+ Phones: []string{"+79001234567", "+74951234567"},
|
|
|
+ Tags: []string{"vip", "active"},
|
|
|
+ Metadata: map[string]string{"source": "website", "referral": "google"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ ID: uuid.New().String(),
|
|
|
+ FullName: "ООО 'ТехноСервис'",
|
|
|
+ Type: ClientTypeLegal,
|
|
|
+ Contractor: true,
|
|
|
+ Email: "office@technoservice.ru",
|
|
|
+ LegalAddress: "125009, г. Москва, ул. Тверская, д. 7",
|
|
|
+ Note: "Основной контрагент",
|
|
|
+ Income: Money{10090, "RUB"},
|
|
|
+ Phones: []string{"+74959876543"},
|
|
|
+ Tags: []string{"partner", "reliable"},
|
|
|
+ Metadata: map[string]string{"industry": "it", "employees": "50+"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ ID: uuid.New().String(),
|
|
|
+ FullName: "ООО 'ГлобалСапплай'",
|
|
|
+ Type: ClientTypeSupplier,
|
|
|
+ Contractor: true,
|
|
|
+ Email: "supply@globalsupply.ru",
|
|
|
+ LegalAddress: "190000, г. Санкт-Петербург, Невский пр., д. 28",
|
|
|
+ Note: "Основной поставщик оборудования",
|
|
|
+ Income: Money{15000, "EUR"},
|
|
|
+ Phones: []string{"+74951112233", "+78123334455"},
|
|
|
+ Tags: []string{"supplier", "international"},
|
|
|
+ Metadata: map[string]string{"currency": "EUR", "delivery_time": "2 weeks"},
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ if offset >= len(clients) {
|
|
|
+ return []ClientInfo{}
|
|
|
+ }
|
|
|
+ if limit+offset > len(clients) {
|
|
|
+ limit = len(clients) - offset
|
|
|
+ }
|
|
|
+
|
|
|
+ return clients[offset : offset+limit]
|
|
|
}
|