amochka 0.1.9__py3-none-any.whl → 0.3.1__py3-none-any.whl

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.
@@ -0,0 +1,346 @@
1
+ -- Миграция: создание таблиц для ETL amoCRM
2
+ -- Структура ПОЛНОСТЬЮ совместима с mybi.ru для работы с существующими dbt-моделями
3
+ -- Схема: public (как в mybi)
4
+ -- ВАЖНО: Все типы данных соответствуют реальной структуре mybi
5
+
6
+ -- ============================================================================
7
+ -- СПРАВОЧНИКИ
8
+ -- ============================================================================
9
+
10
+ -- Справочник дат (точная копия general_dates в mybi)
11
+ CREATE TABLE IF NOT EXISTS general_dates (
12
+ id SERIAL PRIMARY KEY,
13
+ full_date TIMESTAMP NOT NULL UNIQUE,
14
+ year INTEGER,
15
+ quarter INTEGER,
16
+ quarter_label VARCHAR,
17
+ month INTEGER,
18
+ month_label VARCHAR,
19
+ week INTEGER,
20
+ weekday INTEGER,
21
+ weekday_label VARCHAR,
22
+ day INTEGER,
23
+ hour INTEGER,
24
+ minute INTEGER,
25
+ date_hash VARCHAR,
26
+ simple_date DATE,
27
+ time_zone INTEGER -- Добавлено как в mybi
28
+ );
29
+
30
+ CREATE INDEX IF NOT EXISTS idx_general_dates_full_date ON general_dates(full_date);
31
+ CREATE INDEX IF NOT EXISTS idx_general_dates_simple_date ON general_dates(simple_date);
32
+
33
+ -- Воронки
34
+ CREATE TABLE IF NOT EXISTS amocrm_pipelines (
35
+ id SERIAL PRIMARY KEY,
36
+ account_id INTEGER NOT NULL,
37
+ pipeline_id INTEGER NOT NULL,
38
+ name VARCHAR,
39
+ sort INTEGER,
40
+ is_main BOOLEAN DEFAULT FALSE,
41
+ is_unsorted_on BOOLEAN DEFAULT FALSE,
42
+ is_archive BOOLEAN DEFAULT FALSE,
43
+ UNIQUE (account_id, pipeline_id)
44
+ );
45
+
46
+ -- Статусы этапов продаж
47
+ CREATE TABLE IF NOT EXISTS amocrm_statuses (
48
+ id SERIAL PRIMARY KEY,
49
+ account_id INTEGER NOT NULL,
50
+ pipeline_id INTEGER NOT NULL,
51
+ status_id INTEGER NOT NULL,
52
+ name VARCHAR,
53
+ color VARCHAR,
54
+ sort INTEGER,
55
+ is_editable BOOLEAN DEFAULT TRUE,
56
+ type INTEGER DEFAULT 0,
57
+ UNIQUE (account_id, pipeline_id, status_id)
58
+ );
59
+
60
+ -- Пользователи
61
+ CREATE TABLE IF NOT EXISTS amocrm_users (
62
+ id SERIAL PRIMARY KEY,
63
+ account_id INTEGER NOT NULL,
64
+ user_id INTEGER NOT NULL,
65
+ login VARCHAR,
66
+ name VARCHAR,
67
+ phone VARCHAR,
68
+ email VARCHAR,
69
+ group_name VARCHAR,
70
+ group_id INTEGER,
71
+ role_id INTEGER,
72
+ role_name VARCHAR,
73
+ is_admin BOOLEAN DEFAULT FALSE,
74
+ is_active BOOLEAN DEFAULT TRUE,
75
+ is_free BOOLEAN DEFAULT FALSE,
76
+ mail_access BOOLEAN DEFAULT FALSE,
77
+ catalog_access BOOLEAN DEFAULT FALSE,
78
+ UNIQUE (account_id, user_id)
79
+ );
80
+
81
+ -- ============================================================================
82
+ -- ОСНОВНЫЕ ТАБЛИЦЫ ИЗМЕРЕНИЙ
83
+ -- ============================================================================
84
+
85
+ -- Сделки (leads) - точная копия структуры mybi
86
+ CREATE TABLE IF NOT EXISTS amocrm_leads (
87
+ id SERIAL PRIMARY KEY, -- Внутренний ID (автоинкремент)
88
+ account_id INTEGER NOT NULL,
89
+ lead_id INTEGER NOT NULL, -- ID сделки из amoCRM
90
+ name VARCHAR,
91
+ pipeline VARCHAR, -- Название воронки (денормализовано)
92
+ pipeline_id INTEGER,
93
+ status VARCHAR, -- Название статуса (денормализовано)
94
+ status_id INTEGER,
95
+ status_order INTEGER, -- Очередность статуса
96
+ request_id VARCHAR, -- ID заявки (для неразобранного)
97
+ loss_reason VARCHAR, -- Причина отказа (денормализовано)
98
+ loss_reason_id INTEGER,
99
+ is_deleted BOOLEAN DEFAULT FALSE,
100
+ UNIQUE (account_id, lead_id)
101
+ );
102
+
103
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_account_pipeline ON amocrm_leads(account_id, pipeline_id);
104
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_status ON amocrm_leads(status_id);
105
+
106
+ -- Атрибуты сделок (custom fields)
107
+ -- ВАЖНО: leads_id ссылается на amocrm_leads.id (внутренний), НЕ на lead_id!
108
+ CREATE TABLE IF NOT EXISTS amocrm_leads_attributes (
109
+ id SERIAL PRIMARY KEY,
110
+ account_id INTEGER NOT NULL,
111
+ leads_id INTEGER NOT NULL, -- FK на amocrm_leads.id (внутренний!)
112
+ attribute_id VARCHAR NOT NULL, -- field_id из amoCRM (строка)
113
+ name VARCHAR NOT NULL, -- field_name
114
+ value TEXT
115
+ );
116
+
117
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_attributes_leads ON amocrm_leads_attributes(leads_id, account_id);
118
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_attributes_attr ON amocrm_leads_attributes(attribute_id);
119
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_attributes_name ON amocrm_leads_attributes(name);
120
+
121
+ -- Теги сделок
122
+ CREATE TABLE IF NOT EXISTS amocrm_leads_tags (
123
+ id SERIAL PRIMARY KEY,
124
+ account_id INTEGER NOT NULL,
125
+ leads_id INTEGER NOT NULL, -- FK на amocrm_leads.id (внутренний!)
126
+ tag_id INTEGER NOT NULL,
127
+ name VARCHAR NOT NULL
128
+ );
129
+
130
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_tags_leads ON amocrm_leads_tags(leads_id, account_id);
131
+
132
+ -- События сделок (история изменений)
133
+ CREATE TABLE IF NOT EXISTS amocrm_leads_events (
134
+ id SERIAL PRIMARY KEY,
135
+ account_id INTEGER NOT NULL,
136
+ leads_id INTEGER NOT NULL, -- FK на amocrm_leads.id (внутренний!)
137
+ event_id VARCHAR NOT NULL, -- ID события из amoCRM (строка)
138
+ type VARCHAR NOT NULL, -- Тип события
139
+ created_by INTEGER, -- Кто создал событие
140
+ created_at TIMESTAMP, -- Когда создано (без timezone как в mybi)
141
+ value_after TEXT, -- JSON состояния после
142
+ value_before TEXT, -- JSON состояния до
143
+ UNIQUE (account_id, event_id)
144
+ );
145
+
146
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_events_leads ON amocrm_leads_events(leads_id, account_id);
147
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_events_type ON amocrm_leads_events(type);
148
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_events_created ON amocrm_leads_events(created_at);
149
+
150
+ -- Примечания сделок
151
+ CREATE TABLE IF NOT EXISTS amocrm_leads_notes (
152
+ id SERIAL PRIMARY KEY,
153
+ account_id INTEGER NOT NULL,
154
+ leads_id INTEGER NOT NULL, -- FK на amocrm_leads.id (внутренний!)
155
+ creator_id INTEGER,
156
+ responsible_id INTEGER,
157
+ note_id INTEGER NOT NULL,
158
+ note_type VARCHAR,
159
+ note_type_id INTEGER,
160
+ created_at TIMESTAMP,
161
+ updated_at TIMESTAMP,
162
+ text TEXT,
163
+ params TEXT, -- JSON дополнительных параметров
164
+ UNIQUE (account_id, note_id)
165
+ );
166
+
167
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_notes_leads ON amocrm_leads_notes(leads_id, account_id);
168
+
169
+ -- Связь сделок с контактами
170
+ CREATE TABLE IF NOT EXISTS amocrm_leads_contacts (
171
+ id SERIAL PRIMARY KEY,
172
+ account_id INTEGER NOT NULL,
173
+ leads_id INTEGER NOT NULL, -- FK на amocrm_leads.id (внутренний!)
174
+ contacts_id INTEGER NOT NULL, -- FK на amocrm_contacts.id (внутренний!)
175
+ main BOOLEAN DEFAULT FALSE -- Основной контакт
176
+ );
177
+
178
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_contacts_leads ON amocrm_leads_contacts(leads_id, account_id);
179
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_contacts_contacts ON amocrm_leads_contacts(contacts_id, account_id);
180
+
181
+ -- ============================================================================
182
+ -- КОНТАКТЫ
183
+ -- ============================================================================
184
+
185
+ -- Контакты (точная копия структуры mybi)
186
+ CREATE TABLE IF NOT EXISTS amocrm_contacts (
187
+ id SERIAL PRIMARY KEY, -- Внутренний ID (автоинкремент)
188
+ account_id INTEGER NOT NULL,
189
+ contact_id INTEGER NOT NULL, -- ID контакта из amoCRM
190
+ name VARCHAR,
191
+ company VARCHAR,
192
+ post VARCHAR, -- Должность
193
+ phone VARCHAR, -- Телефон(ы)
194
+ email VARCHAR, -- Email
195
+ request_id VARCHAR,
196
+ is_deleted BOOLEAN DEFAULT FALSE,
197
+ first_name VARCHAR,
198
+ last_name VARCHAR,
199
+ UNIQUE (account_id, contact_id)
200
+ );
201
+
202
+ CREATE INDEX IF NOT EXISTS idx_amocrm_contacts_account ON amocrm_contacts(account_id);
203
+ CREATE INDEX IF NOT EXISTS idx_amocrm_contacts_phone ON amocrm_contacts(phone);
204
+
205
+ -- Атрибуты контактов
206
+ CREATE TABLE IF NOT EXISTS amocrm_contacts_attributes (
207
+ id SERIAL PRIMARY KEY,
208
+ account_id INTEGER NOT NULL,
209
+ contacts_id INTEGER NOT NULL, -- FK на amocrm_contacts.id (внутренний!)
210
+ attribute_id VARCHAR NOT NULL,
211
+ name VARCHAR NOT NULL,
212
+ value TEXT
213
+ );
214
+
215
+ CREATE INDEX IF NOT EXISTS idx_amocrm_contacts_attributes_contacts ON amocrm_contacts_attributes(contacts_id, account_id);
216
+
217
+ -- ============================================================================
218
+ -- ТАБЛИЦЫ ФАКТОВ
219
+ -- ============================================================================
220
+
221
+ -- Факты по сделкам (точная копия структуры mybi)
222
+ CREATE TABLE IF NOT EXISTS amocrm_leads_facts (
223
+ id SERIAL PRIMARY KEY,
224
+ account_id INTEGER NOT NULL,
225
+ clientids_id INTEGER, -- ID клиента
226
+ traffic_id INTEGER, -- ID источника трафика
227
+ users_id INTEGER, -- Ответственный (FK на amocrm_users.id)
228
+ leads_id INTEGER NOT NULL, -- FK на amocrm_leads.id (внутренний!)
229
+ contacts_id INTEGER, -- FK на amocrm_contacts.id (внутренний!)
230
+ companies_id INTEGER, -- Компания
231
+ unsorteds_id INTEGER, -- ID неразобранного
232
+ created_id INTEGER, -- FK на general_dates.id
233
+ closed_id INTEGER, -- FK на general_dates.id
234
+ price NUMERIC, -- Сумма сделки (NUMERIC как в mybi)
235
+ created_date TIMESTAMP, -- Дата создания
236
+ modified_date TIMESTAMP, -- Дата последнего изменения
237
+ labor_cost NUMERIC, -- Стоимость труда
238
+ score INTEGER, -- Скоринг
239
+ UNIQUE (account_id, leads_id)
240
+ );
241
+
242
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_facts_leads ON amocrm_leads_facts(leads_id, account_id);
243
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_facts_contacts ON amocrm_leads_facts(contacts_id);
244
+ CREATE INDEX IF NOT EXISTS idx_amocrm_leads_facts_created ON amocrm_leads_facts(created_id);
245
+
246
+ -- Факты по контактам
247
+ CREATE TABLE IF NOT EXISTS amocrm_contacts_facts (
248
+ id SERIAL PRIMARY KEY,
249
+ account_id INTEGER NOT NULL,
250
+ contacts_id INTEGER NOT NULL, -- FK на amocrm_contacts.id (внутренний!)
251
+ companies_id INTEGER,
252
+ users_id INTEGER, -- Ответственный
253
+ registered_id INTEGER, -- FK на general_dates.id
254
+ created_date TIMESTAMP,
255
+ modified_date TIMESTAMP,
256
+ UNIQUE (account_id, contacts_id)
257
+ );
258
+
259
+ -- ============================================================================
260
+ -- ETL STATE (служебная таблица, не из mybi)
261
+ -- ============================================================================
262
+
263
+ CREATE TABLE IF NOT EXISTS etl_state (
264
+ id SERIAL PRIMARY KEY,
265
+ entity_type VARCHAR NOT NULL, -- leads, contacts, events, notes, etc.
266
+ account_id INTEGER NOT NULL,
267
+ pipeline_id INTEGER, -- NULL = все воронки
268
+ last_updated_at TIMESTAMP, -- Последний updated_at из данных
269
+ last_run_at TIMESTAMP, -- Время последнего запуска ETL
270
+ records_loaded INTEGER DEFAULT 0, -- Количество загруженных записей
271
+ error_message TEXT -- Сообщение об ошибке (если была)
272
+ );
273
+
274
+ -- Уникальный индекс с COALESCE для etl_state
275
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_etl_state_unique
276
+ ON etl_state (entity_type, account_id, COALESCE(pipeline_id, 0));
277
+
278
+ -- ============================================================================
279
+ -- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
280
+ -- ============================================================================
281
+
282
+ -- Функция для получения или создания ID даты в general_dates
283
+ -- Принимает TIMESTAMPTZ (timestamp with time zone)
284
+ CREATE OR REPLACE FUNCTION get_or_create_date_id(ts TIMESTAMPTZ)
285
+ RETURNS INTEGER AS $$
286
+ DECLARE
287
+ date_id INTEGER;
288
+ truncated_ts TIMESTAMP;
289
+ BEGIN
290
+ IF ts IS NULL THEN
291
+ RETURN NULL;
292
+ END IF;
293
+
294
+ -- Усекаем до минуты (как в mybi)
295
+ truncated_ts := date_trunc('minute', ts);
296
+
297
+ -- Пытаемся найти существующую запись
298
+ SELECT id INTO date_id FROM general_dates WHERE full_date = truncated_ts;
299
+
300
+ -- Если не нашли, создаём
301
+ IF date_id IS NULL THEN
302
+ INSERT INTO general_dates (
303
+ full_date, year, quarter, quarter_label, month, month_label,
304
+ week, weekday, weekday_label, day, hour, minute, simple_date, time_zone
305
+ ) VALUES (
306
+ truncated_ts,
307
+ EXTRACT(YEAR FROM truncated_ts)::INTEGER,
308
+ EXTRACT(QUARTER FROM truncated_ts)::INTEGER,
309
+ 'Q' || EXTRACT(QUARTER FROM truncated_ts)::TEXT,
310
+ EXTRACT(MONTH FROM truncated_ts)::INTEGER,
311
+ TO_CHAR(truncated_ts, 'Month'),
312
+ EXTRACT(WEEK FROM truncated_ts)::INTEGER,
313
+ EXTRACT(ISODOW FROM truncated_ts)::INTEGER,
314
+ TO_CHAR(truncated_ts, 'Day'),
315
+ EXTRACT(DAY FROM truncated_ts)::INTEGER,
316
+ EXTRACT(HOUR FROM truncated_ts)::INTEGER,
317
+ EXTRACT(MINUTE FROM truncated_ts)::INTEGER,
318
+ truncated_ts::DATE,
319
+ 0 -- time_zone по умолчанию
320
+ )
321
+ ON CONFLICT (full_date) DO NOTHING
322
+ RETURNING id INTO date_id;
323
+
324
+ -- Если INSERT не вернул id (из-за ON CONFLICT), получаем его SELECT-ом
325
+ IF date_id IS NULL THEN
326
+ SELECT id INTO date_id FROM general_dates WHERE full_date = truncated_ts;
327
+ END IF;
328
+ END IF;
329
+
330
+ RETURN date_id;
331
+ END;
332
+ $$ LANGUAGE plpgsql;
333
+
334
+ -- ============================================================================
335
+ -- КОММЕНТАРИИ
336
+ -- ============================================================================
337
+
338
+ COMMENT ON TABLE amocrm_leads IS 'Сделки из amoCRM. Структура идентична mybi.';
339
+ COMMENT ON TABLE amocrm_leads_attributes IS 'Кастомные поля сделок. leads_id = внутренний id из amocrm_leads.';
340
+ COMMENT ON TABLE amocrm_leads_events IS 'События сделок. leads_id = внутренний id из amocrm_leads.';
341
+ COMMENT ON TABLE amocrm_leads_facts IS 'Факты по сделкам. leads_id/contacts_id = внутренние id.';
342
+ COMMENT ON TABLE amocrm_contacts IS 'Контакты из amoCRM. Структура идентична mybi.';
343
+ COMMENT ON TABLE general_dates IS 'Справочник дат для join-ов по created_id/closed_id.';
344
+ COMMENT ON TABLE etl_state IS 'Состояние ETL для инкрементальной загрузки (служебная).';
345
+
346
+ COMMENT ON FUNCTION get_or_create_date_id IS 'Получает или создаёт запись в general_dates для timestamp.';