@lotics/cli 0.283.0 → 0.284.1

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,1682 @@
1
+ # Worked examples
2
+
3
+ Each section is one treatment, cut from a complete model of an invented business: the job, the keys to
4
+ read, where the treatment is wrong, and the excerpt — an app the check passes with no finding, as stated;
5
+ the `records` of every entity it draws; those entities' fields it names, by what each holds and how it is
6
+ drawn; and the templates it makes. Left out: labels, descriptions, `required`, `unique`, `default`, a link's
7
+ pairing, how a computed field computes, and template contents. Choosing among them: the `design` reference.
8
+ Every key: the `model` reference, at the pages each section names under **Keys**.
9
+
10
+ ## Calendar
11
+
12
+ **The job.** Book visits on the days they happen, and see a week's work at a glance.
13
+
14
+ **Read.** `register.layout: "calendar"`; each visit stands over its span because its `records` line holds two dates (`scheduled_for`, `ends_at`).
15
+
16
+ **Keys.** `model/calendar`
17
+
18
+ **Not when.** Rows booked on a resource that two may not share at once — that is `lanes`. Rows with a date nobody plans by (an invoice's date) stay a table.
19
+
20
+ ```json
21
+ {
22
+ "entities": [
23
+ {
24
+ "alias": "service_visit",
25
+ "fields": [
26
+ {"alias": "customer", "type": "select_record_link", "target_entity": "customer", "cardinality": "one"},
27
+ {"alias": "scheduled_for", "type": "date", "format": "datetime"},
28
+ {"alias": "ends_at", "type": "date", "format": "datetime"},
29
+ {"alias": "engineer", "type": "select_record_link", "target_entity": "engineer", "cardinality": "one"},
30
+ {
31
+ "alias": "job",
32
+ "type": "select",
33
+ "options": [{"alias": "inspection", "color": "sky"}, {"alias": "repair", "color": "orange"}]
34
+ },
35
+ {"alias": "booked_on", "type": "date"},
36
+ {"alias": "parts_ready_on", "type": "date"},
37
+ {"alias": "arrived_on", "type": "date"},
38
+ {"alias": "hours", "type": "number", "format": "number", "unit": "h"},
39
+ {"alias": "report", "type": "text", "format": "markdown"},
40
+ {"alias": "completed_on", "type": "date"}
41
+ ]
42
+ }
43
+ ],
44
+ "records": {
45
+ "service_visit": {
46
+ "title": "customer",
47
+ "subtitle": ["scheduled_for", "ends_at"],
48
+ "status": {
49
+ "milestones": ["booked_on", {"field": "parts_ready_on", "when": {"job": ["repair"]}}, "arrived_on", "completed_on"],
50
+ "closed": ["completed_on"]
51
+ },
52
+ "figure": "hours",
53
+ "due": ["scheduled_for"]
54
+ }
55
+ },
56
+ "apps": [
57
+ {
58
+ "alias": "visits",
59
+ "name": "Service visits",
60
+ "description": "Engineers' visits to customers on site, booked and completed by the day.",
61
+ "icon": "calendar",
62
+ "entity": "service_visit",
63
+ "register": {
64
+ "columns": ["engineer", "job"],
65
+ "filters": ["customer"],
66
+ "sort": {"field": "scheduled_for"},
67
+ "layout": "calendar",
68
+ "create": ["customer", "job", "scheduled_for", "ends_at", "engineer"],
69
+ "readings": [
70
+ {"metric": "service_visit", "where": {"job": ["repair"]}, "label": "Repairs"},
71
+ {"breakdown": "service_visit", "by": "engineer", "value": "hours"}
72
+ ]
73
+ },
74
+ "record": {
75
+ "sections": [
76
+ {"title": "Visit", "at": ["booked_on", "parts_ready_on"], "fields": ["job"], "acts": ["reschedule"]},
77
+ {"title": "Report", "at": ["arrived_on"], "fields": ["engineer", "report"], "acts": ["complete"]}
78
+ ]
79
+ },
80
+ "acts": [
81
+ {
82
+ "alias": "complete",
83
+ "label": "Complete visit",
84
+ "requires": ["engineer"],
85
+ "asks": ["hours", {"input": "summary", "label": "What was done", "type": "long_text", "required": true}],
86
+ "set": {"report": "input:summary", "completed_on": "now"}
87
+ },
88
+ {"alias": "reschedule", "label": "Reschedule", "asks": ["scheduled_for", "ends_at"]}
89
+ ]
90
+ }
91
+ ]
92
+ }
93
+ ```
94
+
95
+ ## Lanes
96
+
97
+ **The job.** Put each service visit on an engineer's day by the hour, and see at once who is free between visits.
98
+
99
+ **Read.** `register.layout: "lanes"` with `lanes` naming the one-row link to the resource (the engineer); every engineer stands as a lane, empty or not.
100
+
101
+ **Keys.** `model/lanes`
102
+
103
+ **Not when.** The resource is not a row of the model (a free-text room name), or rows never compete for one — a `calendar` reads them by day.
104
+
105
+ ```json
106
+ {
107
+ "entities": [
108
+ {
109
+ "alias": "service_visit",
110
+ "fields": [
111
+ {"alias": "customer", "type": "select_record_link", "target_entity": "customer", "cardinality": "one"},
112
+ {"alias": "scheduled_for", "type": "date", "format": "datetime"},
113
+ {"alias": "ends_at", "type": "date", "format": "datetime"},
114
+ {"alias": "engineer", "type": "select_record_link", "target_entity": "engineer", "cardinality": "one"},
115
+ {
116
+ "alias": "job",
117
+ "type": "select",
118
+ "options": [{"alias": "inspection", "color": "sky"}, {"alias": "repair", "color": "orange"}]
119
+ },
120
+ {"alias": "booked_on", "type": "date"},
121
+ {"alias": "parts_ready_on", "type": "date"},
122
+ {"alias": "arrived_on", "type": "date"},
123
+ {"alias": "hours", "type": "number", "format": "number", "unit": "h"},
124
+ {"alias": "completed_on", "type": "date"}
125
+ ]
126
+ },
127
+ {
128
+ "alias": "engineer",
129
+ "fields": [
130
+ {"alias": "name", "type": "text"},
131
+ {"alias": "base", "type": "select_record_link", "target_entity": "warehouse", "cardinality": "one"}
132
+ ]
133
+ }
134
+ ],
135
+ "records": {
136
+ "service_visit": {
137
+ "title": "customer",
138
+ "subtitle": ["scheduled_for", "ends_at"],
139
+ "status": {
140
+ "milestones": ["booked_on", {"field": "parts_ready_on", "when": {"job": ["repair"]}}, "arrived_on", "completed_on"],
141
+ "closed": ["completed_on"]
142
+ },
143
+ "figure": "hours",
144
+ "due": ["scheduled_for"]
145
+ },
146
+ "engineer": {"title": "name", "subtitle": ["base"], "party": "person"}
147
+ },
148
+ "apps": [
149
+ {
150
+ "alias": "dispatch",
151
+ "name": "Dispatch",
152
+ "description": "Each engineer's visits across the day, and the free time between them to book into.",
153
+ "icon": "wrench",
154
+ "entity": "service_visit",
155
+ "register": {
156
+ "columns": ["job"],
157
+ "search": ["customer"],
158
+ "layout": "lanes",
159
+ "lanes": "engineer",
160
+ "create": ["customer", "job"]
161
+ },
162
+ "record": {"door": "drawer", "sections": [{"title": "Booking", "fields": ["engineer", "job"]}]}
163
+ }
164
+ ]
165
+ }
166
+ ```
167
+
168
+ ## Gantt
169
+
170
+ **The job.** Read every hire as a bar from the day the machine goes out to the day it is due back, against today.
171
+
172
+ **Read.** `register.layout: "gantt"` and `register.gantt.start`; with no end stated, the bar ends at the entity's first `due`.
173
+
174
+ **Keys.** `model/gantt`
175
+
176
+ **Not when.** Rows that last a day or less (a visit, a shift) — `calendar` or `lanes`; a plan with no dates set yet.
177
+
178
+ ```json
179
+ {
180
+ "entities": [
181
+ {
182
+ "alias": "phieu_thue",
183
+ "fields": [
184
+ {
185
+ "alias": "khach_hang",
186
+ "type": "select_record_link",
187
+ "target_entity": "khach_hang",
188
+ "cardinality": "one"
189
+ },
190
+ {"alias": "so_phieu", "type": "text"},
191
+ {
192
+ "alias": "trang_thai",
193
+ "type": "select",
194
+ "options": [
195
+ {"alias": "moi", "color": "slate"},
196
+ {"alias": "dang_thue", "color": "blue"},
197
+ {"alias": "da_tra", "color": "green"},
198
+ {"alias": "huy", "color": "gray"}
199
+ ]
200
+ },
201
+ {"alias": "phu_trach", "type": "select_member"},
202
+ {"alias": "ngay_giao", "type": "date"},
203
+ {"alias": "han_tra", "type": "date"},
204
+ {"alias": "ngay_tra", "type": "date"},
205
+ {
206
+ "alias": "tinh_trang_tra",
207
+ "type": "select",
208
+ "options": [
209
+ {"alias": "tot", "color": "green"},
210
+ {"alias": "tray_xuoc", "color": "amber"},
211
+ {"alias": "hu_hong", "color": "red"}
212
+ ]
213
+ },
214
+ {"alias": "so_ngay", "type": "formula"},
215
+ {"alias": "tong_tien", "type": "rollup"}
216
+ ]
217
+ }
218
+ ],
219
+ "records": {
220
+ "phieu_thue": {
221
+ "title": "khach_hang",
222
+ "subtitle": ["so_phieu"],
223
+ "status": {"field": "trang_thai", "closed": ["da_tra", "huy"], "history": "lich_su_phieu"},
224
+ "figure": "tong_tien",
225
+ "due": ["han_tra"]
226
+ }
227
+ },
228
+ "apps": [
229
+ {
230
+ "alias": "tien_do_thue",
231
+ "name": "Tiến độ cho thuê",
232
+ "description": "Mỗi phiếu thuê một thanh từ ngày giao máy đến hạn trả, đọc theo hôm nay: phiếu nào sắp đến hạn, phiếu nào quá hạn; kéo thanh để dời ngày giao hoặc gia hạn trả, và nhận máy về khi khách trả.",
233
+ "icon": "calendar-range",
234
+ "theme": {"color": "teal"},
235
+ "entity": "phieu_thue",
236
+ "register": {
237
+ "columns": ["phu_trach"],
238
+ "filters": ["phu_trach", "khach_hang"],
239
+ "layout": "gantt",
240
+ "gantt": {"start": "ngay_giao"},
241
+ "remove": false,
242
+ "create": false,
243
+ "readings": [
244
+ {"metric": "phieu_thue", "where": {"trang_thai": ["dang_thue"]}, "label": "Phiếu đang cho thuê"},
245
+ {
246
+ "metric": "phieu_thue",
247
+ "value": "tong_tien",
248
+ "where": {"trang_thai": ["dang_thue"]},
249
+ "label": "Tiền thuê đang chạy"
250
+ }
251
+ ]
252
+ },
253
+ "record": {
254
+ "sections": [
255
+ {"title": "Phiếu thuê", "fields": ["ngay_giao", "han_tra"]},
256
+ {"title": "Đang thuê", "at": ["dang_thue"], "fields": ["so_ngay", "ngay_tra"], "acts": ["nhan_tra"]}
257
+ ]
258
+ },
259
+ "acts": [
260
+ {
261
+ "alias": "nhan_tra",
262
+ "label": "Nhận máy trả",
263
+ "when": {"trang_thai": ["dang_thue"]},
264
+ "asks": ["tinh_trang_tra"],
265
+ "set": {"trang_thai": "da_tra", "ngay_tra": "now"},
266
+ "confirm": true
267
+ }
268
+ ]
269
+ }
270
+ ]
271
+ }
272
+ ```
273
+
274
+ ## Roster
275
+
276
+ **The job.** See who works which shift on each day of the week or the month.
277
+
278
+ **Read.** `register.layout: "roster"` over the people; `register.roster` names the child whose rows fill the days — one link back and a date on its line.
279
+
280
+ **Keys.** `model/roster`
281
+
282
+ **Not when.** Rows that are themselves the bookings (a stay over several days) — a `calendar` or `lanes` draws them; a roster's row is what the days are filled for.
283
+
284
+ ```json
285
+ {
286
+ "entities": [
287
+ {
288
+ "alias": "nha_si",
289
+ "fields": [
290
+ {"alias": "ho_ten", "type": "text"},
291
+ {
292
+ "alias": "chuyen_mon",
293
+ "type": "select",
294
+ "options": [
295
+ {"alias": "tong_quat", "color": "blue"},
296
+ {"alias": "chinh_nha", "color": "purple"},
297
+ {"alias": "nha_chu", "color": "green"},
298
+ {"alias": "phuc_hinh", "color": "orange"}
299
+ ]
300
+ },
301
+ {"alias": "dien_thoai", "type": "text"}
302
+ ]
303
+ },
304
+ {
305
+ "alias": "lich_truc",
306
+ "fields": [
307
+ {"alias": "nha_si", "type": "select_record_link", "target_entity": "nha_si", "cardinality": "one"},
308
+ {
309
+ "alias": "ca",
310
+ "type": "select",
311
+ "options": [
312
+ {"alias": "sang", "color": "blue"},
313
+ {"alias": "chieu", "color": "purple"},
314
+ {"alias": "ca_ngay", "color": "green"},
315
+ {"alias": "nghi", "color": "gray"}
316
+ ]
317
+ },
318
+ {"alias": "ngay", "type": "date"},
319
+ {"alias": "ghe", "type": "select_record_link", "target_entity": "ghe", "cardinality": "one"}
320
+ ]
321
+ }
322
+ ],
323
+ "records": {
324
+ "nha_si": {"title": "ho_ten", "subtitle": ["chuyen_mon"], "party": "person"},
325
+ "lich_truc": {"title": "ca", "subtitle": ["ngay", "ghe"]}
326
+ },
327
+ "apps": [
328
+ {
329
+ "alias": "lich_truc",
330
+ "name": "Lịch trực",
331
+ "description": "Ai trực ca nào, ở ghế nào, trong tuần và trong tháng.",
332
+ "icon": "users",
333
+ "entity": "nha_si",
334
+ "register": {
335
+ "filters": ["chuyen_mon"],
336
+ "layout": "roster",
337
+ "roster": "lich_truc",
338
+ "create": ["ho_ten", "chuyen_mon", "dien_thoai"]
339
+ }
340
+ }
341
+ ]
342
+ }
343
+ ```
344
+
345
+ ## Cards
346
+
347
+ **The job.** Find an item by its picture, and count what the shelf holds.
348
+
349
+ **Read.** `register.layout: "cards"` over rows whose `records.image` is a photo; `scope` keeps the app inside one warehouse at a time.
350
+
351
+ **Keys.** `model/cards`, `model/records`, `model/apps`
352
+
353
+ **Not when.** Rows with no picture — cards of words are a table drawn worse.
354
+
355
+ ```json
356
+ {
357
+ "entities": [
358
+ {
359
+ "alias": "stock_item",
360
+ "fields": [
361
+ {"alias": "name", "type": "text"},
362
+ {
363
+ "alias": "warehouse",
364
+ "type": "select_record_link",
365
+ "target_entity": "warehouse",
366
+ "cardinality": "one"
367
+ },
368
+ {"alias": "photo", "type": "files"},
369
+ {"alias": "sku", "type": "text"},
370
+ {
371
+ "alias": "category",
372
+ "type": "select",
373
+ "options": [
374
+ {"alias": "pumps", "color": "blue"},
375
+ {"alias": "valves", "color": "teal"},
376
+ {"alias": "seals", "color": "amber"},
377
+ {"alias": "motors", "color": "violet"}
378
+ ]
379
+ },
380
+ {"alias": "on_hand", "type": "number"},
381
+ {"alias": "minimum", "type": "number"},
382
+ {"alias": "price", "type": "number", "format": "currency", "currency": "USD"},
383
+ {"alias": "supplier_page", "type": "text", "format": "link"},
384
+ {"alias": "last_counted", "type": "date"},
385
+ {"alias": "counted_by", "type": "select_member"},
386
+ {"alias": "count_note", "type": "text"}
387
+ ]
388
+ },
389
+ {
390
+ "alias": "order_line",
391
+ "fields": [
392
+ {"alias": "item", "type": "select_record_link", "target_entity": "stock_item", "cardinality": "one"},
393
+ {"alias": "order", "type": "select_record_link", "target_entity": "order", "cardinality": "one"},
394
+ {"alias": "quantity", "type": "number"},
395
+ {"alias": "shipped", "type": "number"},
396
+ {"alias": "picked", "type": "number"},
397
+ {"alias": "unit_price", "type": "number", "format": "currency", "currency": "USD"},
398
+ {"alias": "amount", "type": "formula"},
399
+ {"alias": "category", "type": "lookup"}
400
+ ]
401
+ }
402
+ ],
403
+ "records": {
404
+ "stock_item": {
405
+ "title": "name",
406
+ "subtitle": ["sku", "category"],
407
+ "image": "photo",
408
+ "figure": "on_hand",
409
+ "limits": {"on_hand": 400},
410
+ "gates": {"on_hand": "minimum"}
411
+ },
412
+ "order_line": {
413
+ "title": "item",
414
+ "subtitle": ["order", "unit_price"],
415
+ "figure": "amount",
416
+ "limits": {"shipped": "quantity", "picked": "quantity"}
417
+ }
418
+ },
419
+ "apps": [
420
+ {
421
+ "alias": "stock",
422
+ "name": "Stock",
423
+ "description": "What the store holds of each item, against its shelf and its minimum.",
424
+ "icon": "boxes",
425
+ "theme": {"color": "teal"},
426
+ "entity": "stock_item",
427
+ "scope": "warehouse",
428
+ "register": {
429
+ "columns": ["price"],
430
+ "filters": ["on_hand"],
431
+ "search": ["name", "sku"],
432
+ "layout": "cards",
433
+ "create": false,
434
+ "export": true,
435
+ "readings": [{"breakdown": "stock_item", "by": "category", "value": "on_hand"}]
436
+ },
437
+ "record": {
438
+ "sections": [
439
+ {"title": "Item", "fields": ["price", "minimum", "supplier_page"]},
440
+ {"title": "Last count", "fields": ["last_counted", "counted_by", "count_note"], "acts": ["count"]},
441
+ {"title": "On order", "blocks": [{"rows": "order_line", "columns": ["quantity"]}]}
442
+ ]
443
+ },
444
+ "acts": [
445
+ {
446
+ "alias": "count",
447
+ "label": "Count stock",
448
+ "asks": [
449
+ {"input": "counted", "label": "Units on the shelf", "type": "number", "required": true},
450
+ {"input": "remark", "label": "Remark", "type": "long_text"}
451
+ ],
452
+ "set": {
453
+ "on_hand": "input:counted",
454
+ "last_counted": "now",
455
+ "counted_by": "me",
456
+ "count_note": "input:remark"
457
+ }
458
+ }
459
+ ]
460
+ }
461
+ ]
462
+ }
463
+ ```
464
+
465
+ ## Party
466
+
467
+ **The job.** Keep each customer account — who they are, what they ordered, what was said to them.
468
+
469
+ **Read.** `records.customer.party: "organization"` and its `image` (the logo); a `timeline` of touches, a `trend` of its orders, and its papers `expect`ed by kind.
470
+
471
+ **Keys.** `model/records`, `model/blocks`, `model/reading-blocks`
472
+
473
+ **Not when.** A list of customers is not a job by itself: the customer is a link on the desk whose rows name it, unless the job is the account itself.
474
+
475
+ ```json
476
+ {
477
+ "entities": [
478
+ {
479
+ "alias": "customer",
480
+ "fields": [
481
+ {"alias": "name", "type": "text"},
482
+ {"alias": "logo", "type": "files"},
483
+ {"alias": "code", "type": "text"},
484
+ {
485
+ "alias": "tier",
486
+ "type": "select",
487
+ "options": [
488
+ {"alias": "key", "color": "violet"},
489
+ {"alias": "standard", "color": "sky"},
490
+ {"alias": "lapsed", "color": "gray"}
491
+ ]
492
+ },
493
+ {"alias": "phone", "type": "text"},
494
+ {"alias": "website", "type": "text", "format": "link"},
495
+ {"alias": "account_manager", "type": "select_member"},
496
+ {"alias": "credit_limit", "type": "number", "format": "currency", "currency": "USD"},
497
+ {"alias": "outstanding", "type": "rollup"},
498
+ {"alias": "over_limit", "type": "formula"},
499
+ {
500
+ "alias": "papers_needed",
501
+ "type": "select",
502
+ "options": [
503
+ {"alias": "credit_application", "color": "blue"},
504
+ {"alias": "trade_reference", "color": "violet"},
505
+ {"alias": "vat_certificate", "color": "teal"}
506
+ ],
507
+ "multi": true
508
+ },
509
+ {"alias": "notes", "type": "text", "format": "markdown"}
510
+ ]
511
+ },
512
+ {
513
+ "alias": "order",
514
+ "fields": [
515
+ {"alias": "customer", "type": "select_record_link", "target_entity": "customer", "cardinality": "one"},
516
+ {"alias": "reference", "type": "text"},
517
+ {
518
+ "alias": "stage",
519
+ "type": "select",
520
+ "options": [
521
+ {"alias": "draft", "color": "slate"},
522
+ {"alias": "confirmed", "color": "blue"},
523
+ {"alias": "picking", "color": "amber"},
524
+ {"alias": "shipped", "color": "indigo"},
525
+ {"alias": "delivered", "color": "green"},
526
+ {"alias": "cancelled", "color": "rose"}
527
+ ]
528
+ },
529
+ {"alias": "placed", "type": "date"},
530
+ {"alias": "due_date", "type": "date"},
531
+ {"alias": "total", "type": "rollup"},
532
+ {"alias": "units_ordered", "type": "rollup"},
533
+ {"alias": "units_shipped", "type": "rollup"},
534
+ {"alias": "units_picked", "type": "rollup"},
535
+ {"alias": "notes", "type": "text", "format": "markdown"}
536
+ ]
537
+ },
538
+ {
539
+ "alias": "customer_paper",
540
+ "fields": [
541
+ {
542
+ "alias": "kind",
543
+ "type": "select",
544
+ "options": [
545
+ {"alias": "credit_application", "color": "blue"},
546
+ {"alias": "trade_reference", "color": "violet"},
547
+ {"alias": "vat_certificate", "color": "teal"}
548
+ ]
549
+ },
550
+ {"alias": "customer", "type": "select_record_link", "target_entity": "customer", "cardinality": "one"},
551
+ {"alias": "received_on", "type": "date"},
552
+ {"alias": "file", "type": "files"}
553
+ ]
554
+ },
555
+ {
556
+ "alias": "touch",
557
+ "fields": [
558
+ {"alias": "said", "type": "text"},
559
+ {"alias": "customer", "type": "select_record_link", "target_entity": "customer", "cardinality": "one"},
560
+ {"alias": "by", "type": "select_member"}
561
+ ]
562
+ }
563
+ ],
564
+ "records": {
565
+ "customer": {
566
+ "title": "name",
567
+ "subtitle": ["tier"],
568
+ "image": "logo",
569
+ "party": "organization",
570
+ "figure": "outstanding",
571
+ "limits": {"outstanding": "credit_limit"}
572
+ },
573
+ "order": {
574
+ "title": "reference",
575
+ "subtitle": ["customer", "placed"],
576
+ "status": {"field": "stage", "closed": ["delivered", "cancelled"], "history": "order_stage"},
577
+ "figure": "total",
578
+ "limits": {"units_shipped": "units_ordered", "units_picked": "units_ordered"},
579
+ "due": ["due_date"]
580
+ },
581
+ "customer_paper": {"title": "kind", "image": "file"},
582
+ "touch": {"title": "said"}
583
+ },
584
+ "apps": [
585
+ {
586
+ "alias": "customers",
587
+ "name": "Customers",
588
+ "description": "Who we sell to, what they owe, and what was said.",
589
+ "icon": "users",
590
+ "theme": {"color": "violet"},
591
+ "entity": "customer",
592
+ "reads": "shared",
593
+ "register": {
594
+ "columns": ["phone", "account_manager"],
595
+ "filters": ["account_manager"],
596
+ "sort": {"field": "name"},
597
+ "opens": {"tier": ["key", "standard"]},
598
+ "create": ["name", "code", "credit_limit"],
599
+ "readings": [
600
+ {"breakdown": "customer", "by": "tier", "value": "outstanding"},
601
+ {"breakdown": "customer", "by": "account_manager", "value": "outstanding"}
602
+ ]
603
+ },
604
+ "record": {
605
+ "door": "drawer",
606
+ "sections": [
607
+ {"title": "Contact", "fields": ["phone", "website"]},
608
+ {
609
+ "title": "Account",
610
+ "fields": ["code", "account_manager", "credit_limit"],
611
+ "acts": ["reassign", "lapse", "reinstate"]
612
+ },
613
+ {
614
+ "title": "Orders",
615
+ "blocks": [
616
+ {
617
+ "trend": "order",
618
+ "over": "placed",
619
+ "value": "total",
620
+ "where": {"stage": ["confirmed", "picking", "shipped", "delivered"]},
621
+ "label": "Order value"
622
+ },
623
+ {
624
+ "rows": "order",
625
+ "via": "customer",
626
+ "title": "Open orders",
627
+ "columns": ["due_date"],
628
+ "create": false,
629
+ "where": {"stage": ["draft", "confirmed", "picking", "shipped"]}
630
+ }
631
+ ]
632
+ },
633
+ {
634
+ "title": "Account papers",
635
+ "blocks": [
636
+ {
637
+ "rows": "customer_paper",
638
+ "via": "customer",
639
+ "columns": ["received_on"],
640
+ "expect": "kind",
641
+ "of": "papers_needed"
642
+ }
643
+ ]
644
+ },
645
+ {"title": "Contact log", "blocks": [{"timeline": "touch", "via": "customer"}]},
646
+ {"title": "Notes", "blocks": [{"text": "notes"}]}
647
+ ]
648
+ },
649
+ "acts": [
650
+ {
651
+ "alias": "lapse",
652
+ "label": "Mark lapsed",
653
+ "when": {"tier": ["key", "standard"]},
654
+ "set": {"tier": "lapsed"},
655
+ "confirm": true
656
+ },
657
+ {
658
+ "alias": "reinstate",
659
+ "label": "Reinstate",
660
+ "when": {"tier": ["lapsed"]},
661
+ "asks": [
662
+ {
663
+ "input": "back_to",
664
+ "label": "Back to",
665
+ "type": "select",
666
+ "options": [
667
+ {"alias": "key", "label": "Key account", "color": "violet"},
668
+ {"alias": "standard", "label": "Standard", "color": "sky"}
669
+ ],
670
+ "required": true
671
+ }
672
+ ],
673
+ "set": {"tier": "input:back_to"}
674
+ },
675
+ {
676
+ "alias": "reassign",
677
+ "label": "Reassign",
678
+ "asks": [{"input": "to", "label": "Account manager", "type": "member", "required": true}],
679
+ "set": {"account_manager": "input:to"}
680
+ }
681
+ ],
682
+ "checks": [{"field": "over_limit"}]
683
+ }
684
+ ]
685
+ }
686
+ ```
687
+
688
+ ## Case
689
+
690
+ **The job.** Gather an applicant's papers into one case, make the forms it needs, and file it on time.
691
+
692
+ **Read.** `expect` blocks for the papers brought (one line per kind, the missing ones empty), an act's `templates` for the forms made (ticked and made at once, kept in `into`), and `intake` reading the papers brought.
693
+
694
+ **Keys.** `model/blocks`, `model/acts`
695
+
696
+ **Not when.** A record that needs no papers, or one paper made once — a single `template` act.
697
+
698
+ ```json
699
+ {
700
+ "entities": [
701
+ {
702
+ "alias": "ho_so",
703
+ "fields": [
704
+ {
705
+ "alias": "khach_hang",
706
+ "type": "select_record_link",
707
+ "target_entity": "khach_hang",
708
+ "cardinality": "one"
709
+ },
710
+ {"alias": "du_an", "type": "select_record_link", "target_entity": "du_an", "cardinality": "one"},
711
+ {"alias": "ma_ho_so", "type": "autonumber"},
712
+ {
713
+ "alias": "dich_vu",
714
+ "type": "select",
715
+ "options": [{"alias": "tron_goi", "color": "blue"}, {"alias": "lam_ho_so", "color": "violet"}]
716
+ },
717
+ {
718
+ "alias": "doi_tuong",
719
+ "type": "select",
720
+ "options": [
721
+ {"alias": "thu_nhap_thap", "color": "sky"},
722
+ {"alias": "cong_nhan", "color": "teal"},
723
+ {"alias": "can_bo", "color": "indigo"},
724
+ {"alias": "luc_luong", "color": "green"},
725
+ {"alias": "ho_ngheo", "color": "amber"},
726
+ {"alias": "nguoi_co_cong", "color": "rose"}
727
+ ]
728
+ },
729
+ {
730
+ "alias": "tinh_trang_nha_o",
731
+ "type": "select",
732
+ "options": [{"alias": "chua_co_nha", "color": "gray"}, {"alias": "nha_chat", "color": "orange"}]
733
+ },
734
+ {"alias": "giay_chung_nhan_so", "type": "text"},
735
+ {"alias": "dien_tich_nha", "type": "number"},
736
+ {"alias": "sale", "type": "select_member"},
737
+ {"alias": "xu_ly", "type": "select_member"},
738
+ {"alias": "can_bo_sung", "type": "boolean"},
739
+ {"alias": "phi_dich_vu", "type": "number", "format": "currency", "currency": "VND"},
740
+ {
741
+ "alias": "loai_can",
742
+ "type": "select",
743
+ "options": [
744
+ {"alias": "studio", "color": "sky"},
745
+ {"alias": "mot_pn", "color": "blue"},
746
+ {"alias": "hai_pn", "color": "indigo"},
747
+ {"alias": "ba_pn", "color": "violet"}
748
+ ]
749
+ },
750
+ {"alias": "can_boc", "type": "text"},
751
+ {"alias": "hoa_hong_sale", "type": "number", "format": "currency", "currency": "VND"},
752
+ {"alias": "ngay_thu_phi", "type": "date"},
753
+ {"alias": "ngay_nhan_giay_to", "type": "date"},
754
+ {"alias": "ngay_lap_ho_so", "type": "date"},
755
+ {"alias": "ngay_nop_cdt", "type": "date"},
756
+ {"alias": "ngay_cdt_duyet", "type": "date"},
757
+ {"alias": "ngay_so_xd_duyet", "type": "date"},
758
+ {"alias": "ngay_boc_tham", "type": "date"},
759
+ {"alias": "ngay_ky_hdmb", "type": "date"},
760
+ {"alias": "ngay_ban_giao", "type": "date"},
761
+ {"alias": "bo_ho_so", "type": "files"},
762
+ {"alias": "hop_dong_mua_ban", "type": "files"},
763
+ {"alias": "giay_to", "type": "select_record_link", "target_entity": "giay_to", "cardinality": "many"},
764
+ {
765
+ "alias": "thanh_vien",
766
+ "type": "select_record_link",
767
+ "target_entity": "thanh_vien",
768
+ "cardinality": "many"
769
+ },
770
+ {
771
+ "alias": "phieu_thu",
772
+ "type": "select_record_link",
773
+ "target_entity": "phieu_thu",
774
+ "cardinality": "many"
775
+ },
776
+ {"alias": "dien_thoai", "type": "lookup"},
777
+ {"alias": "cccd", "type": "lookup"},
778
+ {"alias": "ngay_sinh", "type": "lookup"},
779
+ {"alias": "dia_chi", "type": "lookup"},
780
+ {"alias": "da_thu", "type": "rollup"},
781
+ {"alias": "chua_thu_phi", "type": "formula"},
782
+ {"alias": "con_no_phi", "type": "formula"},
783
+ {"alias": "vuot_thu_nhap", "type": "formula"},
784
+ {"alias": "da_hoan_tat", "type": "formula"}
785
+ ]
786
+ },
787
+ {
788
+ "alias": "thanh_vien",
789
+ "fields": [
790
+ {"alias": "ho_ten", "type": "text"},
791
+ {"alias": "ho_so", "type": "select_record_link", "target_entity": "ho_so", "cardinality": "one"},
792
+ {
793
+ "alias": "quan_he",
794
+ "type": "select",
795
+ "options": [
796
+ {"alias": "nguoi_dung_don", "color": "blue"},
797
+ {"alias": "vo_chong", "color": "violet"},
798
+ {"alias": "con", "color": "teal"},
799
+ {"alias": "bo_me", "color": "amber"},
800
+ {"alias": "khac", "color": "gray"}
801
+ ]
802
+ },
803
+ {"alias": "ngay_sinh", "type": "date"},
804
+ {"alias": "cccd", "type": "text"},
805
+ {"alias": "thu_nhap", "type": "number", "format": "currency", "currency": "VND"}
806
+ ]
807
+ },
808
+ {
809
+ "alias": "phieu_thu",
810
+ "fields": [
811
+ {
812
+ "alias": "khoan",
813
+ "type": "select",
814
+ "options": [
815
+ {"alias": "dat_coc", "color": "blue"},
816
+ {"alias": "tat_toan", "color": "indigo"},
817
+ {"alias": "phi_ho_so", "color": "violet"}
818
+ ]
819
+ },
820
+ {"alias": "ho_so", "type": "select_record_link", "target_entity": "ho_so", "cardinality": "one"},
821
+ {"alias": "so_tien", "type": "number", "format": "currency", "currency": "VND"},
822
+ {
823
+ "alias": "hinh_thuc",
824
+ "type": "select",
825
+ "options": [
826
+ {"alias": "tien_mat", "color": "green", "mark": {"kind": "icon", "name": "banknote"}},
827
+ {"alias": "chuyen_khoan", "color": "blue", "mark": {"kind": "icon", "name": "arrow-right-left"}}
828
+ ]
829
+ },
830
+ {
831
+ "alias": "trang_thai",
832
+ "type": "select",
833
+ "options": [
834
+ {"alias": "cho_thu", "color": "amber"},
835
+ {"alias": "da_thu", "color": "green"},
836
+ {"alias": "huy", "color": "gray"}
837
+ ]
838
+ },
839
+ {"alias": "ngay_thu", "type": "date"},
840
+ {"alias": "nguoi_thu", "type": "select_member"},
841
+ {"alias": "da_chot", "type": "formula"}
842
+ ]
843
+ },
844
+ {
845
+ "alias": "giay_to",
846
+ "fields": [
847
+ {
848
+ "alias": "loai",
849
+ "type": "select",
850
+ "options": [
851
+ {"alias": "don_dang_ky", "color": "blue"},
852
+ {"alias": "can_cuoc", "color": "sky"},
853
+ {"alias": "giay_doi_tuong", "color": "violet"},
854
+ {"alias": "xac_nhan_nha_o", "color": "teal"},
855
+ {"alias": "xac_nhan_thu_nhap", "color": "amber"},
856
+ {"alias": "hon_nhan", "color": "indigo"}
857
+ ]
858
+ },
859
+ {"alias": "ho_so", "type": "select_record_link", "target_entity": "ho_so", "cardinality": "one"},
860
+ {"alias": "ngay_nhan", "type": "date"},
861
+ {"alias": "can_sua", "type": "text"},
862
+ {"alias": "tep", "type": "files"},
863
+ {"alias": "phai_sua", "type": "formula"}
864
+ ]
865
+ }
866
+ ],
867
+ "records": {
868
+ "ho_so": {
869
+ "title": "khach_hang",
870
+ "subtitle": ["dich_vu"],
871
+ "status": {
872
+ "milestones": [
873
+ {"field": "ngay_thu_phi", "when": {"dich_vu": ["lam_ho_so"]}},
874
+ "ngay_nhan_giay_to",
875
+ {"field": "ngay_lap_ho_so", "when": {"dich_vu": ["tron_goi"]}},
876
+ {"field": "ngay_nop_cdt", "when": {"dich_vu": ["tron_goi"]}},
877
+ {"field": "ngay_cdt_duyet", "when": {"dich_vu": ["tron_goi"]}},
878
+ {"field": "ngay_so_xd_duyet", "when": {"dich_vu": ["tron_goi"]}},
879
+ {"field": "ngay_boc_tham", "when": {"dich_vu": ["tron_goi"]}},
880
+ {"field": "ngay_ky_hdmb", "when": {"dich_vu": ["tron_goi"]}},
881
+ {"field": "ngay_ban_giao", "when": {"dich_vu": ["lam_ho_so"]}}
882
+ ],
883
+ "closed": ["ngay_ky_hdmb", "ngay_ban_giao"]
884
+ },
885
+ "figure": "phi_dich_vu"
886
+ },
887
+ "thanh_vien": {"title": "ho_ten", "subtitle": ["quan_he", "cccd"], "party": "person", "figure": "thu_nhap"},
888
+ "phieu_thu": {
889
+ "title": "khoan",
890
+ "subtitle": ["hinh_thuc", "ngay_thu"],
891
+ "status": {"field": "trang_thai", "closed": ["da_thu", "huy"]},
892
+ "figure": "so_tien"
893
+ },
894
+ "giay_to": {"title": "loai", "image": "tep", "starts": {"ngay_nhan": "today"}}
895
+ },
896
+ "templates": [
897
+ {"alias": "don_dang_ky", "type": "html", "label": "Đơn đăng ký mua nhà ở xã hội"},
898
+ {"alias": "mau_02_chua_co_nha", "type": "html", "label": "Mẫu 02 — Xác nhận chưa có nhà ở"},
899
+ {"alias": "mau_03_nha_chat", "type": "html", "label": "Mẫu 03 — Xác nhận nhà ở dưới 15 m²/người"},
900
+ {"alias": "mau_05_thu_nhap", "type": "html", "label": "Mẫu 05 — Xác nhận thu nhập"},
901
+ {"alias": "danh_sach_nop", "type": "html", "label": "Danh sách hồ sơ nộp chủ đầu tư"}
902
+ ],
903
+ "apps": [
904
+ {
905
+ "alias": "ho_so",
906
+ "name": "Hồ sơ nhà ở xã hội",
907
+ "description": "Bàn làm hồ sơ của từng dự án: nhận giấy tờ, lập và nộp hồ sơ cho chủ đầu tư, theo từng khâu duyệt tới bốc thăm và ký hợp đồng — hoặc làm hồ sơ rồi bàn giao cho khách tự nộp.",
908
+ "icon": "folder-open",
909
+ "theme": {"color": "blue"},
910
+ "entity": "ho_so",
911
+ "scope": "du_an",
912
+ "register": {
913
+ "columns": ["xu_ly", "can_bo_sung", "doi_tuong"],
914
+ "filters": ["xu_ly", "can_bo_sung"],
915
+ "search": ["khach_hang", "ma_ho_so", "dien_thoai"],
916
+ "create": ["khach_hang", "dich_vu", "doi_tuong", "tinh_trang_nha_o", "loai_can", "phi_dich_vu", "sale"],
917
+ "export": true,
918
+ "readings": [
919
+ {"metric": "ho_so", "where": {"can_bo_sung": true}, "label": "Chờ khách bổ sung"},
920
+ {"metric": "ho_so", "value": "da_thu", "over": "ngay_nhan_giay_to", "label": "Phí đã thu"},
921
+ {"breakdown": "ho_so", "by": "doi_tuong"}
922
+ ]
923
+ },
924
+ "record": {
925
+ "sections": [
926
+ {
927
+ "title": "Người đứng đơn",
928
+ "fields": [
929
+ "ma_ho_so",
930
+ "dien_thoai",
931
+ "cccd",
932
+ "ngay_sinh",
933
+ "dia_chi",
934
+ "doi_tuong",
935
+ "tinh_trang_nha_o",
936
+ "sale",
937
+ "xu_ly",
938
+ "can_bo_sung"
939
+ ],
940
+ "blocks": [
941
+ {
942
+ "rows": "thanh_vien",
943
+ "title": "Hộ gia đình",
944
+ "columns": ["ngay_sinh"],
945
+ "create": ["ho_ten", "quan_he", "ngay_sinh", "cccd", "thu_nhap"]
946
+ }
947
+ ],
948
+ "acts": ["doc_giay_to", "giao_xu_ly", "chuyen_du_an"]
949
+ },
950
+ {
951
+ "title": "Nhà hiện có",
952
+ "fields": ["giay_chung_nhan_so", "dien_tich_nha"],
953
+ "when": {"tinh_trang_nha_o": ["nha_chat"]}
954
+ },
955
+ {
956
+ "title": "Phí dịch vụ",
957
+ "blocks": [
958
+ {
959
+ "rows": "phieu_thu",
960
+ "title": "Phiếu thu",
961
+ "columns": ["so_tien"],
962
+ "create": ["khoan", "so_tien", "hinh_thuc"],
963
+ "where": {"khoan": ["dat_coc", "tat_toan"]},
964
+ "expect": "khoan",
965
+ "when": {"dich_vu": ["tron_goi"]}
966
+ },
967
+ {
968
+ "rows": "phieu_thu",
969
+ "title": "Phiếu thu",
970
+ "columns": ["so_tien"],
971
+ "create": ["khoan", "so_tien", "hinh_thuc"],
972
+ "where": {"khoan": ["phi_ho_so"]},
973
+ "expect": "khoan",
974
+ "when": {"dich_vu": ["lam_ho_so"]}
975
+ }
976
+ ]
977
+ },
978
+ {
979
+ "title": "Giấy tờ",
980
+ "at": ["ngay_thu_phi"],
981
+ "blocks": [
982
+ {
983
+ "rows": "giay_to",
984
+ "title": "Giấy tờ",
985
+ "columns": ["ngay_nhan"],
986
+ "create": ["loai", "ngay_nhan", "can_sua", "tep"],
987
+ "expect": "loai"
988
+ }
989
+ ]
990
+ },
991
+ {
992
+ "title": "Bộ hồ sơ",
993
+ "description": "Các mẫu đơn in từ thông tin của hồ sơ.",
994
+ "at": ["ngay_nhan_giay_to", "ngay_lap_ho_so"],
995
+ "acts": ["tao_giay_to", "nop_cdt"]
996
+ },
997
+ {
998
+ "title": "Chủ đầu tư",
999
+ "description": "Hồ sơ trọn gói nộp chủ đầu tư, chờ chủ đầu tư rồi Sở Xây dựng duyệt trước ngày bốc thăm.",
1000
+ "at": ["ngay_nop_cdt", "ngay_cdt_duyet", "ngay_so_xd_duyet"],
1001
+ "fields": ["loai_can"],
1002
+ "when": {"dich_vu": ["tron_goi"]}
1003
+ },
1004
+ {
1005
+ "title": "Căn hộ",
1006
+ "at": ["ngay_boc_tham"],
1007
+ "fields": ["can_boc", "hoa_hong_sale"],
1008
+ "blocks": [{"files": ["hop_dong_mua_ban"]}],
1009
+ "when": {"dich_vu": ["tron_goi"]}
1010
+ }
1011
+ ]
1012
+ },
1013
+ "acts": [
1014
+ {
1015
+ "alias": "doc_giay_to",
1016
+ "label": "Đọc giấy tờ",
1017
+ "intake": "giay_to",
1018
+ "fills": [
1019
+ "doi_tuong",
1020
+ "tinh_trang_nha_o",
1021
+ "giay_chung_nhan_so",
1022
+ "dien_tich_nha",
1023
+ "khach_hang.ho_ten",
1024
+ "khach_hang.cccd",
1025
+ "khach_hang.ngay_sinh",
1026
+ "khach_hang.dia_chi",
1027
+ "thanh_vien"
1028
+ ]
1029
+ },
1030
+ {
1031
+ "alias": "tao_giay_to",
1032
+ "label": "Tạo giấy tờ",
1033
+ "templates": [
1034
+ {"template": "don_dang_ky"},
1035
+ {"template": "mau_02_chua_co_nha", "when": {"tinh_trang_nha_o": ["chua_co_nha"]}},
1036
+ {"template": "mau_03_nha_chat", "when": {"tinh_trang_nha_o": ["nha_chat"]}},
1037
+ {"template": "mau_05_thu_nhap", "when": {"doi_tuong": ["thu_nhap_thap"]}}
1038
+ ],
1039
+ "into": "bo_ho_so"
1040
+ },
1041
+ {
1042
+ "alias": "nop_cdt",
1043
+ "label": "Nộp chủ đầu tư",
1044
+ "when": {"dich_vu": ["tron_goi"]},
1045
+ "requires": ["bo_ho_so", "loai_can"],
1046
+ "asks": ["loai_can"],
1047
+ "set": {"ngay_nop_cdt": "now"},
1048
+ "confirm": true
1049
+ },
1050
+ {
1051
+ "alias": "giao_xu_ly",
1052
+ "label": "Giao người xử lý",
1053
+ "asks": [{"input": "nguoi", "label": "Người xử lý", "type": "member", "required": true}],
1054
+ "set": {"xu_ly": "input:nguoi"}
1055
+ },
1056
+ {
1057
+ "alias": "chuyen_du_an",
1058
+ "label": "Chuyển dự án",
1059
+ "asks": [{"input": "du_an_moi", "label": "Sang dự án", "type": "link", "entity": "du_an", "required": true}],
1060
+ "set": {"du_an": "input:du_an_moi"},
1061
+ "confirm": true
1062
+ },
1063
+ {
1064
+ "alias": "in_danh_sach",
1065
+ "label": "In danh sách nộp chủ đầu tư",
1066
+ "on": "view",
1067
+ "template": "danh_sach_nop"
1068
+ },
1069
+ {
1070
+ "alias": "xac_nhan_thu",
1071
+ "label": "Xác nhận đã thu",
1072
+ "of": "phieu_thu",
1073
+ "when": {"trang_thai": ["cho_thu"]},
1074
+ "asks": ["hinh_thuc"],
1075
+ "set": {"trang_thai": "da_thu", "ngay_thu": "now", "nguoi_thu": "me"},
1076
+ "confirm": true
1077
+ },
1078
+ {
1079
+ "alias": "huy_phieu",
1080
+ "label": "Hủy phiếu",
1081
+ "of": "phieu_thu",
1082
+ "when": {"trang_thai": ["cho_thu"]},
1083
+ "set": {"trang_thai": "huy"},
1084
+ "danger": true
1085
+ }
1086
+ ],
1087
+ "checks": [
1088
+ {"field": "chua_thu_phi", "blocks": ["nop_cdt"]},
1089
+ {"field": "con_no_phi"},
1090
+ {"field": "vuot_thu_nhap"},
1091
+ {"field": "da_hoan_tat", "blocks": ["delete", "doc_giay_to"]},
1092
+ {"field": "da_chot", "of": "phieu_thu", "blocks": ["edit"]},
1093
+ {"field": "phai_sua", "of": "giay_to"}
1094
+ ]
1095
+ }
1096
+ ]
1097
+ }
1098
+ ```
1099
+
1100
+ ## Calls
1101
+
1102
+ **The job.** Call a lead from their record, and keep what was said as a touch on its timeline.
1103
+
1104
+ **Read.** An act's `record` (into the `timeline` child: audio, transcript, when and who) and `fills` (the touch's fields an agent fills from the transcript).
1105
+
1106
+ **Keys.** `model/acts`, `model/blocks`
1107
+
1108
+ **Not when.** Calls made and logged elsewhere — a `timeline` with its own add keeps the note.
1109
+
1110
+ ```json
1111
+ {
1112
+ "entities": [
1113
+ {
1114
+ "alias": "khach_tiem_nang",
1115
+ "fields": [
1116
+ {"alias": "ho_ten", "type": "text"},
1117
+ {"alias": "ten_cong_ty", "type": "text"},
1118
+ {
1119
+ "alias": "vai_tro",
1120
+ "type": "select",
1121
+ "options": [
1122
+ {"alias": "chu_doanh_nghiep", "color": "emerald"},
1123
+ {"alias": "ke_toan_truong", "color": "blue"},
1124
+ {"alias": "quan_ly", "color": "teal"},
1125
+ {"alias": "nhan_vien", "color": "sky"},
1126
+ {"alias": "chua_ro", "color": "zinc"}
1127
+ ]
1128
+ },
1129
+ {
1130
+ "alias": "loai_hinh",
1131
+ "type": "select",
1132
+ "options": [
1133
+ {"alias": "thuong_mai", "color": "blue"},
1134
+ {"alias": "san_xuat", "color": "amber"},
1135
+ {"alias": "dich_vu", "color": "violet"},
1136
+ {"alias": "xay_dung", "color": "orange"},
1137
+ {"alias": "ho_kinh_doanh", "color": "teal"},
1138
+ {"alias": "chua_ro", "color": "zinc"}
1139
+ ]
1140
+ },
1141
+ {
1142
+ "alias": "quy_mo",
1143
+ "type": "select",
1144
+ "options": [
1145
+ {"alias": "q_1_10", "color": "sky"},
1146
+ {"alias": "q_11_50", "color": "blue"},
1147
+ {"alias": "q_51_200", "color": "indigo"},
1148
+ {"alias": "q_200", "color": "violet"},
1149
+ {"alias": "chua_ro", "color": "zinc"}
1150
+ ]
1151
+ },
1152
+ {"alias": "so_dien_thoai", "type": "text"},
1153
+ {"alias": "zalo", "type": "text"},
1154
+ {"alias": "email", "type": "text"},
1155
+ {"alias": "trang_ca_nhan", "type": "text", "format": "link"},
1156
+ {"alias": "anh", "type": "files"},
1157
+ {
1158
+ "alias": "trang_thai",
1159
+ "type": "select",
1160
+ "options": [
1161
+ {"alias": "moi", "color": "gray"},
1162
+ {"alias": "du_dieu_kien", "color": "blue"},
1163
+ {"alias": "da_lien_he", "color": "amber"},
1164
+ {"alias": "da_chuyen_doi", "color": "emerald"},
1165
+ {"alias": "loai", "color": "red"}
1166
+ ]
1167
+ },
1168
+ {
1169
+ "alias": "ly_do_loai",
1170
+ "type": "select",
1171
+ "options": [
1172
+ {"alias": "khong_phu_hop", "color": "orange"},
1173
+ {"alias": "qua_nho", "color": "amber"},
1174
+ {"alias": "chua_co_ngan_sach", "color": "yellow"},
1175
+ {"alias": "sai_nguoi", "color": "blue"},
1176
+ {"alias": "yeu_cau_dung", "color": "red"},
1177
+ {"alias": "trung", "color": "zinc"}
1178
+ ]
1179
+ },
1180
+ {"alias": "vi_sao_du_dieu_kien", "type": "text"},
1181
+ {"alias": "phu_trach", "type": "select_member"},
1182
+ {"alias": "nguon", "type": "select_record_link", "target_entity": "kenh", "cardinality": "one"},
1183
+ {"alias": "lien_he", "type": "select_record_link", "target_entity": "lien_he", "cardinality": "one"},
1184
+ {"alias": "cong_ty", "type": "select_record_link", "target_entity": "cong_ty", "cardinality": "one"},
1185
+ {"alias": "ngay_chuyen_doi", "type": "date", "format": "datetime", "timezone": "Asia/Ho_Chi_Minh"},
1186
+ {"alias": "ghi_chu", "type": "text", "format": "markdown"},
1187
+ {"alias": "tin_hieu", "type": "select_record_link", "target_entity": "tin_hieu"},
1188
+ {"alias": "buoc_tiep", "type": "select_record_link", "target_entity": "buoc_tiep"},
1189
+ {"alias": "tuong_tac", "type": "select_record_link", "target_entity": "tuong_tac"},
1190
+ {"alias": "tin_hieu_moi", "type": "lookup"},
1191
+ {"alias": "do_nong", "type": "formula"},
1192
+ {"alias": "han_buoc_tiep", "type": "rollup"},
1193
+ {"alias": "khong_lien_he", "type": "formula"}
1194
+ ]
1195
+ },
1196
+ {
1197
+ "alias": "tin_hieu",
1198
+ "fields": [
1199
+ {"alias": "tom_tat", "type": "text"},
1200
+ {"alias": "noi_dung", "type": "text", "format": "markdown"},
1201
+ {"alias": "anh_chup", "type": "files"},
1202
+ {"alias": "lien_ket", "type": "text", "format": "link"},
1203
+ {"alias": "kenh", "type": "select_record_link", "target_entity": "kenh", "cardinality": "one"},
1204
+ {
1205
+ "alias": "loai",
1206
+ "type": "select",
1207
+ "options": [
1208
+ {"alias": "hoi_dich_vu", "color": "emerald"},
1209
+ {"alias": "than_phien", "color": "blue"},
1210
+ {"alias": "tuyen_ke_toan", "color": "amber"},
1211
+ {"alias": "moi_thanh_lap", "color": "violet"},
1212
+ {"alias": "gioi_thieu", "color": "teal"},
1213
+ {"alias": "danh_ba", "color": "zinc"},
1214
+ {"alias": "nhieu", "color": "stone"}
1215
+ ]
1216
+ },
1217
+ {"alias": "ngay_dang", "type": "date", "timezone": "Asia/Ho_Chi_Minh"},
1218
+ {"alias": "ngay_xay_ra", "type": "formula"}
1219
+ ]
1220
+ },
1221
+ {
1222
+ "alias": "buoc_tiep",
1223
+ "fields": [
1224
+ {"alias": "vi_sao_luc_nay", "type": "text"},
1225
+ {
1226
+ "alias": "viec",
1227
+ "type": "select",
1228
+ "options": [
1229
+ {"alias": "binh_luan", "color": "blue", "mark": {"kind": "icon", "name": "message-square"}},
1230
+ {"alias": "nhan_facebook", "color": "violet", "mark": {"kind": "brand", "name": "facebook"}},
1231
+ {"alias": "nhan_zalo", "color": "sky", "mark": {"kind": "brand", "name": "zalo"}},
1232
+ {"alias": "goi_dien", "color": "teal", "mark": {"kind": "icon", "name": "phone"}},
1233
+ {"alias": "gui_email", "color": "indigo", "mark": {"kind": "icon", "name": "mail"}},
1234
+ {"alias": "gui_bang_gia", "color": "amber", "mark": {"kind": "icon", "name": "receipt"}}
1235
+ ]
1236
+ },
1237
+ {"alias": "ban_nhap", "type": "text", "format": "markdown"},
1238
+ {"alias": "han", "type": "date", "format": "datetime", "timezone": "Asia/Ho_Chi_Minh"},
1239
+ {
1240
+ "alias": "tinh_trang",
1241
+ "type": "select",
1242
+ "options": [
1243
+ {"alias": "cho", "color": "blue"},
1244
+ {"alias": "xong", "color": "emerald"},
1245
+ {"alias": "bo_qua", "color": "zinc"}
1246
+ ]
1247
+ }
1248
+ ]
1249
+ },
1250
+ {
1251
+ "alias": "tuong_tac",
1252
+ "fields": [
1253
+ {"alias": "tom_tat", "type": "text"},
1254
+ {"alias": "thoi_diem", "type": "date", "format": "datetime", "timezone": "Asia/Ho_Chi_Minh"},
1255
+ {
1256
+ "alias": "qua",
1257
+ "type": "select",
1258
+ "options": [
1259
+ {"alias": "goi_dien", "color": "teal", "mark": {"kind": "icon", "name": "phone"}},
1260
+ {"alias": "zalo", "color": "sky", "mark": {"kind": "brand", "name": "zalo"}},
1261
+ {"alias": "facebook", "color": "blue", "mark": {"kind": "brand", "name": "facebook"}},
1262
+ {"alias": "email", "color": "indigo", "mark": {"kind": "icon", "name": "mail"}},
1263
+ {"alias": "gap_truc_tiep", "color": "emerald", "mark": {"kind": "icon", "name": "users"}},
1264
+ {"alias": "khac", "color": "zinc", "mark": {"kind": "icon", "name": "ellipsis"}}
1265
+ ]
1266
+ },
1267
+ {
1268
+ "alias": "phan_hoi",
1269
+ "type": "select",
1270
+ "options": [
1271
+ {"alias": "dang_cho", "color": "blue"},
1272
+ {"alias": "da_tra_loi", "color": "green"},
1273
+ {"alias": "quan_tam", "color": "amber"},
1274
+ {"alias": "khong_phu_hop", "color": "red"},
1275
+ {"alias": "khong_phan_hoi", "color": "zinc"},
1276
+ {"alias": "sai_so", "color": "stone"}
1277
+ ]
1278
+ },
1279
+ {"alias": "ghi_am", "type": "files"},
1280
+ {"alias": "nguyen_van", "type": "text", "format": "markdown"},
1281
+ {"alias": "hen_lai", "type": "date", "timezone": "Asia/Ho_Chi_Minh"},
1282
+ {"alias": "nguoi_thuc_hien", "type": "select_member"}
1283
+ ]
1284
+ }
1285
+ ],
1286
+ "records": {
1287
+ "khach_tiem_nang": {
1288
+ "title": "ho_ten",
1289
+ "subtitle": ["ten_cong_ty"],
1290
+ "image": "anh",
1291
+ "party": "person",
1292
+ "status": {"field": "trang_thai", "closed": ["da_chuyen_doi", "loai"], "history": "lich_su_trang_thai"},
1293
+ "due": ["han_buoc_tiep"]
1294
+ },
1295
+ "tin_hieu": {"title": "tom_tat", "subtitle": ["kenh", "ngay_xay_ra"], "image": "anh_chup"},
1296
+ "buoc_tiep": {
1297
+ "title": "vi_sao_luc_nay",
1298
+ "subtitle": ["viec"],
1299
+ "status": {"field": "tinh_trang", "closed": ["xong", "bo_qua"]},
1300
+ "due": ["han"]
1301
+ },
1302
+ "tuong_tac": {"title": "tom_tat", "subtitle": ["qua", "phan_hoi"]}
1303
+ },
1304
+ "apps": [
1305
+ {
1306
+ "alias": "khach_tiem_nang",
1307
+ "name": "Khách tiềm năng",
1308
+ "description": "Mọi người mình có thể bán dịch vụ, từ tín hiệu đầu tiên đến khi chuyển vào CRM: mở một khách để đọc vì sao nên gọi lúc này, liên hệ ngay, ghi lại lần chạm, xếp bước tiếp theo, và chuyển đổi khi đã có cuộc trò chuyện.",
1309
+ "icon": "radar",
1310
+ "theme": {"color": "violet"},
1311
+ "entity": "khach_tiem_nang",
1312
+ "register": {
1313
+ "columns": ["phu_trach", "han_buoc_tiep", "tin_hieu_moi"],
1314
+ "filters": ["phu_trach", "tin_hieu_moi"],
1315
+ "create": ["ho_ten", "trang_ca_nhan", "so_dien_thoai", "ten_cong_ty", "nguon"],
1316
+ "readings": [
1317
+ {"breakdown": "khach_tiem_nang", "by": "phu_trach"},
1318
+ {"trend": "khach_tiem_nang", "over": "ngay_chuyen_doi", "label": "Khách chuyển đổi"}
1319
+ ]
1320
+ },
1321
+ "record": {
1322
+ "sections": [
1323
+ {
1324
+ "title": "Đánh giá",
1325
+ "at": ["moi"],
1326
+ "fields": ["vai_tro", "loai_hinh", "quy_mo", "vi_sao_du_dieu_kien", "do_nong"],
1327
+ "blocks": [
1328
+ {
1329
+ "rows": "tin_hieu",
1330
+ "title": "Tín hiệu",
1331
+ "columns": ["loai", "noi_dung"],
1332
+ "create": ["tom_tat", "anh_chup", "loai", "noi_dung", "kenh", "lien_ket", "ngay_dang"]
1333
+ }
1334
+ ],
1335
+ "acts": ["danh_gia_dat"]
1336
+ },
1337
+ {
1338
+ "title": "Liên hệ",
1339
+ "at": ["du_dieu_kien"],
1340
+ "fields": ["so_dien_thoai", "phu_trach", "trang_ca_nhan", "zalo", "email", "ghi_chu"],
1341
+ "blocks": [
1342
+ {
1343
+ "rows": "buoc_tiep",
1344
+ "title": "Bước tiếp theo",
1345
+ "columns": ["ban_nhap"],
1346
+ "create": ["vi_sao_luc_nay", "viec", "han", "ban_nhap"]
1347
+ },
1348
+ {
1349
+ "timeline": "tuong_tac",
1350
+ "title": "Tương tác",
1351
+ "create": ["tom_tat", "qua", "phan_hoi", "thoi_diem", "hen_lai"]
1352
+ }
1353
+ ],
1354
+ "acts": ["ghi_cuoc_goi", "da_lien_he"]
1355
+ },
1356
+ {
1357
+ "title": "Chuyển đổi",
1358
+ "description": "Khách đã trả lời và muốn đi tiếp: chuyển đổi tạo liên hệ và công ty trong sổ khách hàng.",
1359
+ "at": ["da_lien_he"],
1360
+ "fields": ["nguon", "lien_he", "cong_ty"],
1361
+ "acts": ["chuyen_doi"]
1362
+ },
1363
+ {
1364
+ "title": "Loại",
1365
+ "description": "Khách không đi tiếp: ghi lý do để lần sau không tìm lại.",
1366
+ "at": ["loai"],
1367
+ "fields": ["ly_do_loai"],
1368
+ "acts": ["loai"]
1369
+ }
1370
+ ]
1371
+ },
1372
+ "acts": [
1373
+ {
1374
+ "alias": "danh_gia_dat",
1375
+ "label": "Đủ điều kiện",
1376
+ "when": {"trang_thai": ["moi"]},
1377
+ "requires": ["vi_sao_du_dieu_kien"],
1378
+ "set": {"trang_thai": "du_dieu_kien"}
1379
+ },
1380
+ {
1381
+ "alias": "ghi_cuoc_goi",
1382
+ "label": "Ghi âm cuộc gọi",
1383
+ "when": {"trang_thai": ["du_dieu_kien"]},
1384
+ "record": {
1385
+ "into": "tuong_tac",
1386
+ "audio": "ghi_am",
1387
+ "transcript": "nguyen_van",
1388
+ "at": "thoi_diem",
1389
+ "by": "nguoi_thuc_hien"
1390
+ },
1391
+ "fills": ["tom_tat", "phan_hoi"]
1392
+ },
1393
+ {
1394
+ "alias": "da_lien_he",
1395
+ "label": "Đánh dấu đã liên hệ",
1396
+ "when": {"trang_thai": ["du_dieu_kien"]},
1397
+ "set": {"trang_thai": "da_lien_he"}
1398
+ },
1399
+ {
1400
+ "alias": "chuyen_doi",
1401
+ "label": "Chuyển đổi",
1402
+ "when": {"trang_thai": ["da_lien_he"]},
1403
+ "workflow": "chuyen_doi_khach",
1404
+ "confirm": true
1405
+ },
1406
+ {
1407
+ "alias": "loai",
1408
+ "label": "Loại",
1409
+ "when": {"trang_thai": ["moi", "du_dieu_kien", "da_lien_he"]},
1410
+ "asks": ["ly_do_loai"],
1411
+ "set": {"trang_thai": "loai"},
1412
+ "danger": true
1413
+ },
1414
+ {
1415
+ "alias": "lam_xong",
1416
+ "label": "Đã làm",
1417
+ "of": "buoc_tiep",
1418
+ "when": {"tinh_trang": ["cho"]},
1419
+ "set": {"tinh_trang": "xong"}
1420
+ }
1421
+ ],
1422
+ "checks": [{"field": "khong_lien_he", "resolve": "da_lien_he"}]
1423
+ }
1424
+ ]
1425
+ }
1426
+ ```
1427
+
1428
+ ## Approval
1429
+
1430
+ **The job.** Raise a payment request, see it through approval, and read what is waiting at the top of the list.
1431
+
1432
+ **Read.** `records.de_nghi.status` with its `history` (each move: when and who), sections staged by `at`, and `register.readings` leading with the headline number.
1433
+
1434
+ **Keys.** `model/records`, `model/record-page`, `model/readings`
1435
+
1436
+ **Not when.** Rows that never move — a reference list (a price list, a catalog) has no status and no headline number.
1437
+
1438
+ ```json
1439
+ {
1440
+ "entities": [
1441
+ {
1442
+ "alias": "de_nghi",
1443
+ "fields": [
1444
+ {"alias": "noi_dung", "type": "text"},
1445
+ {"alias": "so_de_nghi", "type": "text"},
1446
+ {
1447
+ "alias": "loai",
1448
+ "type": "select",
1449
+ "options": [
1450
+ {"alias": "thanh_toan_ncc", "color": "violet"},
1451
+ {"alias": "nhan_cong", "color": "indigo"},
1452
+ {"alias": "chi_phi_cong_truong", "color": "sky"},
1453
+ {"alias": "tam_ung", "color": "amber"},
1454
+ {"alias": "hoan_ung", "color": "teal"}
1455
+ ]
1456
+ },
1457
+ {"alias": "du_an", "type": "select_record_link", "target_entity": "du_an", "cardinality": "one"},
1458
+ {
1459
+ "alias": "nha_cung_cap",
1460
+ "type": "select_record_link",
1461
+ "target_entity": "nha_cung_cap",
1462
+ "cardinality": "one"
1463
+ },
1464
+ {"alias": "so_tien", "type": "number", "format": "currency", "currency": "VND"},
1465
+ {"alias": "han_thanh_toan", "type": "date"},
1466
+ {
1467
+ "alias": "trang_thai",
1468
+ "type": "select",
1469
+ "options": [
1470
+ {"alias": "nhap", "color": "slate"},
1471
+ {"alias": "cho_duyet", "color": "amber"},
1472
+ {"alias": "da_duyet", "color": "blue"},
1473
+ {"alias": "da_chi", "color": "green"},
1474
+ {"alias": "tu_choi", "color": "rose"}
1475
+ ]
1476
+ },
1477
+ {"alias": "nguoi_de_nghi", "type": "select_member"},
1478
+ {"alias": "chung_tu", "type": "files"},
1479
+ {"alias": "nguoi_duyet", "type": "select_member"},
1480
+ {"alias": "duyet_luc", "type": "date", "format": "datetime"},
1481
+ {"alias": "ly_do_tu_choi", "type": "text"},
1482
+ {"alias": "phieu_chi", "type": "files"},
1483
+ {"alias": "ngan_sach_du_an", "type": "lookup"},
1484
+ {"alias": "da_duyet_du_an", "type": "lookup"},
1485
+ {"alias": "vuot_ngan_sach", "type": "formula"},
1486
+ {"alias": "sat_ngan_sach", "type": "formula"},
1487
+ {"alias": "da_gui", "type": "formula"}
1488
+ ]
1489
+ }
1490
+ ],
1491
+ "records": {
1492
+ "de_nghi": {
1493
+ "title": "noi_dung",
1494
+ "subtitle": ["du_an"],
1495
+ "status": {"field": "trang_thai", "closed": ["da_chi", "tu_choi"], "history": "lich_su_duyet"},
1496
+ "figure": "so_tien",
1497
+ "limits": {"da_duyet_du_an": "ngan_sach_du_an"},
1498
+ "due": ["han_thanh_toan"],
1499
+ "starts": {"nguoi_de_nghi": "me"}
1500
+ }
1501
+ },
1502
+ "apps": [
1503
+ {
1504
+ "alias": "de_nghi_chi",
1505
+ "name": "Đề nghị thanh toán",
1506
+ "description": "Chỉ huy trưởng lập đề nghị chi cho dự án của mình, kèm chứng từ, rồi gửi giám đốc duyệt.",
1507
+ "icon": "hand-coins",
1508
+ "theme": {"color": "amber"},
1509
+ "entity": "de_nghi",
1510
+ "register": {
1511
+ "columns": ["nguoi_de_nghi"],
1512
+ "filters": ["nguoi_de_nghi", "du_an"],
1513
+ "create": ["noi_dung", "loai", "du_an", "nha_cung_cap", "so_tien", "han_thanh_toan", "nguoi_de_nghi"],
1514
+ "readings": [
1515
+ {
1516
+ "metric": "de_nghi",
1517
+ "value": "so_tien",
1518
+ "where": {"trang_thai": ["da_duyet"]},
1519
+ "label": "Đã duyệt, chờ chi"
1520
+ },
1521
+ {"trend": "de_nghi", "over": "han_thanh_toan", "value": "so_tien"}
1522
+ ]
1523
+ },
1524
+ "record": {
1525
+ "door": "drawer",
1526
+ "sections": [
1527
+ {
1528
+ "title": "Đề nghị",
1529
+ "at": ["nhap"],
1530
+ "fields": ["so_de_nghi", "loai", "nha_cung_cap", "han_thanh_toan", "nguoi_de_nghi"],
1531
+ "blocks": [{"files": ["chung_tu"], "title": "Chứng từ kèm"}],
1532
+ "acts": ["gui_duyet"]
1533
+ },
1534
+ {
1535
+ "title": "Duyệt",
1536
+ "at": ["cho_duyet"],
1537
+ "fields": ["nguoi_duyet", "duyet_luc", "ly_do_tu_choi"],
1538
+ "acts": ["rut_lai"]
1539
+ },
1540
+ {"title": "Đã chi", "at": ["da_chi"], "blocks": [{"files": ["phieu_chi"], "title": "Phiếu chi"}]}
1541
+ ]
1542
+ },
1543
+ "acts": [
1544
+ {
1545
+ "alias": "gui_duyet",
1546
+ "label": "Gửi duyệt",
1547
+ "when": {"trang_thai": ["nhap"]},
1548
+ "requires": ["chung_tu", "nguoi_de_nghi"],
1549
+ "set": {"trang_thai": "cho_duyet"}
1550
+ },
1551
+ {
1552
+ "alias": "rut_lai",
1553
+ "label": "Rút lại",
1554
+ "when": {"trang_thai": ["cho_duyet"]},
1555
+ "set": {"trang_thai": "nhap"},
1556
+ "confirm": true
1557
+ }
1558
+ ],
1559
+ "checks": [{"field": "da_gui", "blocks": ["edit"]}, {"field": "vuot_ngan_sach"}, {"field": "sat_ngan_sach"}]
1560
+ }
1561
+ ]
1562
+ }
1563
+ ```
1564
+
1565
+ ## Dashboard
1566
+
1567
+ **The job.** The owner's question — what came in, what went out, where it went, and what is still unmatched — answered over one period.
1568
+
1569
+ **Read.** `dashboard`: `metric`s with `over` (the period windows them), a `trend`, `breakdown`s, and a `list` of rows to act on opening in their app.
1570
+
1571
+ **Keys.** `model/dashboards`, `model/readings`
1572
+
1573
+ **Not when.** One job's headline numbers — those lead that job's register as its `readings`.
1574
+
1575
+ ```json
1576
+ {
1577
+ "entities": [
1578
+ {
1579
+ "alias": "giao_dich",
1580
+ "fields": [
1581
+ {"alias": "noi_dung", "type": "text"},
1582
+ {"alias": "ngay", "type": "date"},
1583
+ {
1584
+ "alias": "loai",
1585
+ "type": "select",
1586
+ "options": [{"alias": "thu", "color": "green"}, {"alias": "chi", "color": "rose"}]
1587
+ },
1588
+ {"alias": "so_tien", "type": "number", "format": "currency", "currency": "VND"},
1589
+ {"alias": "bien_dong", "type": "formula"},
1590
+ {
1591
+ "alias": "doi_tuong",
1592
+ "type": "select_record_link",
1593
+ "target_entity": "doi_tuong",
1594
+ "cardinality": "one"
1595
+ },
1596
+ {
1597
+ "alias": "danh_muc",
1598
+ "type": "select",
1599
+ "options": [
1600
+ {"alias": "doanh_thu", "color": "green"},
1601
+ {"alias": "hang_hoa", "color": "blue"},
1602
+ {"alias": "van_chuyen", "color": "indigo"},
1603
+ {"alias": "nhan_su", "color": "violet"},
1604
+ {"alias": "van_phong", "color": "sky"},
1605
+ {"alias": "thue_phi", "color": "slate"},
1606
+ {"alias": "ngan_hang", "color": "teal"},
1607
+ {"alias": "khac", "color": "gray"}
1608
+ ]
1609
+ },
1610
+ {"alias": "sao_ke", "type": "select_record_link", "target_entity": "sao_ke", "cardinality": "one"},
1611
+ {
1612
+ "alias": "trang_thai",
1613
+ "type": "select",
1614
+ "options": [
1615
+ {"alias": "chua_khop", "color": "amber"},
1616
+ {"alias": "da_khop", "color": "green"},
1617
+ {"alias": "khong_can_hd", "color": "slate"}
1618
+ ]
1619
+ }
1620
+ ]
1621
+ }
1622
+ ],
1623
+ "records": {
1624
+ "giao_dich": {
1625
+ "title": "noi_dung",
1626
+ "subtitle": ["ngay"],
1627
+ "status": {"field": "trang_thai", "closed": ["da_khop", "khong_can_hd"], "history": "lich_su_khop"},
1628
+ "figure": "bien_dong"
1629
+ }
1630
+ },
1631
+ "apps": [
1632
+ {
1633
+ "alias": "dong_tien",
1634
+ "name": "Dòng tiền",
1635
+ "description": "Giám đốc xem trong kỳ: tiền vào, tiền ra, dòng tiền ròng qua các kỳ, chi vào đâu, thu từ ai, và những dòng sao kê còn chưa khớp.",
1636
+ "icon": "chart-line",
1637
+ "theme": {"color": "emerald"},
1638
+ "dashboard": [
1639
+ {
1640
+ "metric": "giao_dich",
1641
+ "value": "so_tien",
1642
+ "where": {"loai": ["thu"]},
1643
+ "over": "ngay",
1644
+ "label": "Tiền vào"
1645
+ },
1646
+ {
1647
+ "metric": "giao_dich",
1648
+ "value": "so_tien",
1649
+ "where": {"loai": ["chi"]},
1650
+ "over": "ngay",
1651
+ "better": "down",
1652
+ "label": "Tiền ra"
1653
+ },
1654
+ {"trend": "giao_dich", "over": "ngay", "value": "bien_dong", "label": "Dòng tiền ròng"},
1655
+ {
1656
+ "breakdown": "giao_dich",
1657
+ "by": "danh_muc",
1658
+ "value": "so_tien",
1659
+ "where": {"loai": ["chi"]},
1660
+ "over": "ngay",
1661
+ "label": "Chi theo danh mục"
1662
+ },
1663
+ {
1664
+ "breakdown": "giao_dich",
1665
+ "by": "doi_tuong",
1666
+ "value": "so_tien",
1667
+ "where": {"loai": ["thu"]},
1668
+ "over": "ngay",
1669
+ "label": "Thu theo khách hàng"
1670
+ },
1671
+ {
1672
+ "list": "giao_dich",
1673
+ "where": {"trang_thai": ["chua_khop"]},
1674
+ "columns": ["sao_ke"],
1675
+ "app": "doi_chieu",
1676
+ "label": "Dòng chưa khớp"
1677
+ }
1678
+ ]
1679
+ }
1680
+ ]
1681
+ }
1682
+ ```