qube_rails 0.2.0

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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +161 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +967 -0
  5. data/app/assets/javascripts/qube_rails/question.js +457 -0
  6. data/app/assets/stylesheets/qube_rails.css +1115 -0
  7. data/app/controllers/concerns/qube_rails/run_authorization.rb +37 -0
  8. data/app/controllers/qube_rails/connections_controller.rb +141 -0
  9. data/app/controllers/qube_rails/questions_controller.rb +132 -0
  10. data/app/controllers/qube_rails/runs_controller.rb +54 -0
  11. data/app/controllers/qube_rails/storyboard_controller.rb +50 -0
  12. data/app/controllers/qube_rails/webhooks_controller.rb +99 -0
  13. data/app/jobs/qube_rails/prune_runs_job.rb +18 -0
  14. data/app/jobs/qube_rails/refresh_job.rb +18 -0
  15. data/app/jobs/qube_rails/webhook_job.rb +43 -0
  16. data/app/models/qube_rails/application_record.rb +17 -0
  17. data/app/models/qube_rails/connection.rb +303 -0
  18. data/app/models/qube_rails/notification.rb +12 -0
  19. data/app/models/qube_rails/queued_request.rb +66 -0
  20. data/app/models/qube_rails/quickbooks_record.rb +36 -0
  21. data/app/models/qube_rails/request_delivery.rb +12 -0
  22. data/app/models/qube_rails/run.rb +187 -0
  23. data/app/models/qube_rails/sync_mark.rb +21 -0
  24. data/app/views/qube_rails/_error.html.erb +13 -0
  25. data/app/views/qube_rails/_question.html.erb +21 -0
  26. data/app/views/qube_rails/_run_status.html.erb +22 -0
  27. data/app/views/qube_rails/question/_choice.html.erb +20 -0
  28. data/app/views/qube_rails/question/_details.html.erb +6 -0
  29. data/app/views/qube_rails/question/_field.html.erb +15 -0
  30. data/app/views/qube_rails/question/_footer.html.erb +7 -0
  31. data/app/views/qube_rails/question/_message.html.erb +2 -0
  32. data/app/views/qube_rails/question/_pick.html.erb +19 -0
  33. data/app/views/qube_rails/question/_picks.html.erb +6 -0
  34. data/app/views/qube_rails/question/_question.html.erb +24 -0
  35. data/app/views/qube_rails/question/fields/_boolean.html.erb +7 -0
  36. data/app/views/qube_rails/question/fields/_number.html.erb +7 -0
  37. data/app/views/qube_rails/question/fields/_rows.html.erb +23 -0
  38. data/app/views/qube_rails/question/fields/_search.html.erb +23 -0
  39. data/app/views/qube_rails/question/fields/_select.html.erb +8 -0
  40. data/app/views/qube_rails/question/fields/_text.html.erb +7 -0
  41. data/app/views/qube_rails/question/rows/_cell.html.erb +24 -0
  42. data/app/views/qube_rails/question/rows/_row.html.erb +22 -0
  43. data/app/views/qube_rails/question/rows/_segmented.html.erb +14 -0
  44. data/app/views/qube_rails/run_status/_detail.html.erb +7 -0
  45. data/app/views/qube_rails/run_status/_redeliver.html.erb +5 -0
  46. data/app/views/qube_rails/run_status/_retry.html.erb +4 -0
  47. data/app/views/qube_rails/run_status/_stall.html.erb +3 -0
  48. data/app/views/qube_rails/run_status/_status.html.erb +21 -0
  49. data/app/views/qube_rails/run_status/_summary.html.erb +2 -0
  50. data/app/views/qube_rails/storyboard/show.html.erb +86 -0
  51. data/config/locales/en.yml +46 -0
  52. data/config/routes.rb +35 -0
  53. data/db/migrate/20261025000000_create_qube_tables.rb +83 -0
  54. data/db/migrate/20261026230000_add_qube_sync_status_indexes.rb +13 -0
  55. data/db/migrate/20261027000000_create_qube_sync_marks.rb +17 -0
  56. data/db/migrate/20261028000000_create_qube_requests.rb +38 -0
  57. data/lib/generators/qube_rails/install_generator.rb +47 -0
  58. data/lib/generators/qube_rails/templates/connection_events.rb.tt +38 -0
  59. data/lib/generators/qube_rails/templates/initializer.rb.tt +82 -0
  60. data/lib/generators/qube_rails/workflows_generator.rb +77 -0
  61. data/lib/qube_rails/backend.rb +70 -0
  62. data/lib/qube_rails/cache_dedupe.rb +34 -0
  63. data/lib/qube_rails/configuration.rb +319 -0
  64. data/lib/qube_rails/connection_events.rb +167 -0
  65. data/lib/qube_rails/database_dedupe.rb +28 -0
  66. data/lib/qube_rails/engine.rb +33 -0
  67. data/lib/qube_rails/json_coder.rb +43 -0
  68. data/lib/qube_rails/operation.rb +464 -0
  69. data/lib/qube_rails/question_view.rb +219 -0
  70. data/lib/qube_rails/request.rb +289 -0
  71. data/lib/qube_rails/rows_view.rb +146 -0
  72. data/lib/qube_rails/run_status_view.rb +71 -0
  73. data/lib/qube_rails/storyboard/ask_about_add.json +60 -0
  74. data/lib/qube_rails/storyboard/choose.json +51 -0
  75. data/lib/qube_rails/storyboard/enum_select.json +56 -0
  76. data/lib/qube_rails/storyboard/missing_record.json +141 -0
  77. data/lib/qube_rails/storyboard/name_clash.json +59 -0
  78. data/lib/qube_rails/storyboard/near_match.json +77 -0
  79. data/lib/qube_rails/storyboard/number_boolean.json +54 -0
  80. data/lib/qube_rails/storyboard/record_search.json +90 -0
  81. data/lib/qube_rails/storyboard/refusal.json +53 -0
  82. data/lib/qube_rails/storyboard/rows.json +359 -0
  83. data/lib/qube_rails/storyboard.rb +165 -0
  84. data/lib/qube_rails/sync_status.rb +124 -0
  85. data/lib/qube_rails/version.rb +5 -0
  86. data/lib/qube_rails/view_helpers.rb +29 -0
  87. data/lib/qube_rails.rb +201 -0
  88. metadata +180 -0
@@ -0,0 +1,141 @@
1
+ {
2
+ "title": "A missing customer",
3
+ "note": "add_transaction's ask_missing with one record: the invoice names a customer QuickBooks doesn't have and the app sent no add for it, so the person says whether to create it or use one QuickBooks has. Create it needs nothing more for a customer; Use an existing one shows the search.",
4
+ "prompt": {
5
+ "id": "add_transaction / ask_missing",
6
+ "kind": "decision",
7
+ "token": "Mc7pR4tKw2NbY9xQ",
8
+ "message": "Some records this transaction uses aren't in QuickBooks yet. For each one, create it or pick one QuickBooks already has.",
9
+ "data": {
10
+ "operation": "invoice",
11
+ "missing": [
12
+ {
13
+ "kind": "customer",
14
+ "full_name": "Lone Star Supply",
15
+ "paths": [
16
+ "customer_ref"
17
+ ]
18
+ },
19
+ {
20
+ "kind": "item",
21
+ "full_name": "Vinyl Banner 3x6",
22
+ "paths": [
23
+ "invoice_line_add.0.item_ref"
24
+ ]
25
+ },
26
+ {
27
+ "kind": "item",
28
+ "full_name": "Hardware Kit",
29
+ "paths": [
30
+ "invoice_line_add.1.item_ref"
31
+ ]
32
+ }
33
+ ]
34
+ },
35
+ "details": [
36
+ {
37
+ "label": "Adding",
38
+ "value": "Invoice"
39
+ },
40
+ {
41
+ "label": "Number",
42
+ "value": "1042"
43
+ },
44
+ {
45
+ "label": "For",
46
+ "value": "Lone Star Supply"
47
+ }
48
+ ],
49
+ "options": [],
50
+ "choices": [
51
+ {
52
+ "id": "proceed",
53
+ "label": "Add the transaction",
54
+ "description": null,
55
+ "detail": null,
56
+ "fields": [
57
+ {
58
+ "name": "records",
59
+ "type": "rows",
60
+ "label": "Not in QuickBooks yet",
61
+ "required": true,
62
+ "columns": [
63
+ {
64
+ "name": "action",
65
+ "type": "string",
66
+ "label": "What to do",
67
+ "required": true,
68
+ "kind": null,
69
+ "enum": [
70
+ {
71
+ "value": "create",
72
+ "label": "Create it"
73
+ },
74
+ {
75
+ "value": "use",
76
+ "label": "Use an existing one"
77
+ }
78
+ ],
79
+ "when": null
80
+ },
81
+ {
82
+ "name": "list_id",
83
+ "type": "record",
84
+ "label": "Existing record",
85
+ "required": true,
86
+ "kind": null,
87
+ "enum": null,
88
+ "when": {
89
+ "action": [
90
+ "use"
91
+ ]
92
+ }
93
+ }
94
+ ],
95
+ "rows": [
96
+ {
97
+ "key": "customer_ref",
98
+ "label": "Lone Star Supply",
99
+ "detail": "Customer",
100
+ "columns": [
101
+ "action",
102
+ "list_id"
103
+ ],
104
+ "values": {},
105
+ "kinds": {
106
+ "list_id": "customer"
107
+ }
108
+ }
109
+ ]
110
+ }
111
+ ]
112
+ },
113
+ {
114
+ "id": "cancel",
115
+ "label": "Cancel",
116
+ "description": "Stop here. Nothing is created.",
117
+ "detail": null,
118
+ "fields": []
119
+ }
120
+ ]
121
+ },
122
+ "search": {
123
+ "records.list_id": [
124
+ {
125
+ "value": "80000001-1700000000",
126
+ "label": "Riverbend Electric",
127
+ "detail": "Customer"
128
+ },
129
+ {
130
+ "value": "80000002-1700000000",
131
+ "label": "Riverbend Supply Co.",
132
+ "detail": "Customer"
133
+ },
134
+ {
135
+ "value": "80000003-1700000000",
136
+ "label": "Lone Star Supply",
137
+ "detail": "Customer"
138
+ }
139
+ ]
140
+ }
141
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "title": "A name clash",
3
+ "note": "ensure's ask_use: the name is already in QuickBooks. Use that record, add this one under another name, or cancel.",
4
+ "prompt": {
5
+ "id": "ensure / ask_use",
6
+ "kind": "decision",
7
+ "token": "Wg5qU7vAs3PhB5dD",
8
+ "message": "\"Riverbend Electric\" is already in QuickBooks. Use that record, add this one under another name, or cancel.",
9
+ "data": {
10
+ "kind": "customer",
11
+ "name": "Riverbend Electric",
12
+ "existing": {
13
+ "list": "customer"
14
+ }
15
+ },
16
+ "details": [
17
+ {
18
+ "label": "Customer",
19
+ "value": "Riverbend Electric"
20
+ },
21
+ {
22
+ "label": "Held by",
23
+ "value": "Customer"
24
+ }
25
+ ],
26
+ "options": [],
27
+ "choices": [
28
+ {
29
+ "id": "use_existing",
30
+ "label": "Use the existing record",
31
+ "description": "Carry on with the record QuickBooks already has. Nothing new is created.",
32
+ "detail": null,
33
+ "fields": []
34
+ },
35
+ {
36
+ "id": "rename",
37
+ "label": "Add it under another name",
38
+ "description": "Try the add again under a name you choose.",
39
+ "detail": null,
40
+ "fields": [
41
+ {
42
+ "name": "name",
43
+ "type": "string",
44
+ "label": "New name",
45
+ "required": true
46
+ }
47
+ ]
48
+ },
49
+ {
50
+ "id": "cancel",
51
+ "label": "Cancel",
52
+ "description": "Stop here. Nothing is created.",
53
+ "detail": null,
54
+ "fields": []
55
+ }
56
+ ]
57
+ },
58
+ "search": {}
59
+ }
@@ -0,0 +1,77 @@
1
+ {
2
+ "title": "A near match",
3
+ "note": "resolve's ask: QuickBooks has no record with exactly that name. The similar ones are choices (detail says why), then a search for another one, none of these, or cancel. With an add from the caller, \"None of these\" says the record is added as new (adds: true); without one it reads just \"None of these\".",
4
+ "prompt": {
5
+ "choices": [
6
+ {
7
+ "description": null,
8
+ "detail": "similar name · ap@riverbend.example · 512-555-0142",
9
+ "fields": [],
10
+ "id": "use:1",
11
+ "label": "Riverbend Electric"
12
+ },
13
+ {
14
+ "description": null,
15
+ "detail": null,
16
+ "fields": [
17
+ {
18
+ "kind": "customer",
19
+ "label": "Search for a different one",
20
+ "name": "list_id",
21
+ "required": true,
22
+ "type": "record"
23
+ }
24
+ ],
25
+ "id": "use",
26
+ "label": "Use this one"
27
+ },
28
+ {
29
+ "description": null,
30
+ "detail": null,
31
+ "fields": [],
32
+ "id": "none",
33
+ "label": "None of these: add \"Riverbend\" as new"
34
+ },
35
+ {
36
+ "description": null,
37
+ "detail": null,
38
+ "fields": [],
39
+ "id": "cancel",
40
+ "label": "Cancel"
41
+ }
42
+ ],
43
+ "data": {
44
+ "name": "Riverbend"
45
+ },
46
+ "details": [
47
+ {
48
+ "label": "Customer",
49
+ "value": "Riverbend"
50
+ }
51
+ ],
52
+ "id": "resolve / ask",
53
+ "kind": "decision",
54
+ "message": "No record has exactly that name. Pick the one you meant, say none of these is it, or cancel.",
55
+ "options": [],
56
+ "token": "Vq3kT9xRb2LmW8sZ"
57
+ },
58
+ "search": {
59
+ "list_id": [
60
+ {
61
+ "value": "80000001-1700000000",
62
+ "label": "Riverbend Electric",
63
+ "detail": "Customer · Riverbend Electric LLC · ap@riverbend.example · 512-555-0142 · Austin 78701"
64
+ },
65
+ {
66
+ "value": "80000002-1700000000",
67
+ "label": "Riverbend Supply Co.",
68
+ "detail": "Customer"
69
+ },
70
+ {
71
+ "value": "80000003-1700000000",
72
+ "label": "Lone Star Supply",
73
+ "detail": "Customer"
74
+ }
75
+ ]
76
+ }
77
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "title": "Number and yes/no fields",
3
+ "note": "A number field posts what was typed and is sent as a number; a boolean is a checkbox, sent as true or false. No shipped template asks these; a chart of your own can.",
4
+ "prompt": {
5
+ "id": "adjust / ask_quantity",
6
+ "kind": "decision",
7
+ "token": "Vf4pT6uZr2NgA4cC",
8
+ "message": "QuickBooks shows fewer on hand than this adjustment takes. Say how many to take, or cancel.",
9
+ "data": {
10
+ "on_hand": 3
11
+ },
12
+ "details": [
13
+ {
14
+ "label": "Item",
15
+ "value": "Vinyl Banner 3x6"
16
+ },
17
+ {
18
+ "label": "On hand",
19
+ "value": "3"
20
+ }
21
+ ],
22
+ "options": [],
23
+ "choices": [
24
+ {
25
+ "id": "adjust",
26
+ "label": "Adjust",
27
+ "description": null,
28
+ "detail": null,
29
+ "fields": [
30
+ {
31
+ "name": "quantity",
32
+ "type": "number",
33
+ "label": "Quantity",
34
+ "required": true
35
+ },
36
+ {
37
+ "name": "allow_negative",
38
+ "type": "boolean",
39
+ "label": "Allow a negative quantity on hand",
40
+ "required": false
41
+ }
42
+ ]
43
+ },
44
+ {
45
+ "id": "cancel",
46
+ "label": "Cancel",
47
+ "description": null,
48
+ "detail": null,
49
+ "fields": []
50
+ }
51
+ ]
52
+ },
53
+ "search": {}
54
+ }
@@ -0,0 +1,90 @@
1
+ {
2
+ "title": "A pick from the records there are",
3
+ "note": "resolve's ask_pick: nothing has that name and nothing may be created for it (an account here), so the person picks one: the first active records as rows, then a search over every record of the kind. The search box asks your app's search route and fills a hidden input with the pick's ListID.",
4
+ "prompt": {
5
+ "id": "resolve / ask_pick",
6
+ "kind": "decision",
7
+ "token": "Sb4kQ2rTn8LmV1xZ",
8
+ "message": "QuickBooks has nothing named \"Consulting Income\". Pick the one to use instead, or cancel.",
9
+ "data": {
10
+ "name": "Consulting Income",
11
+ "kind": "account"
12
+ },
13
+ "details": [
14
+ {
15
+ "label": "Account",
16
+ "value": "Consulting Income"
17
+ }
18
+ ],
19
+ "options": [],
20
+ "choices": [
21
+ {
22
+ "id": "use:1",
23
+ "label": "Sales",
24
+ "description": null,
25
+ "detail": null,
26
+ "fields": []
27
+ },
28
+ {
29
+ "id": "use:2",
30
+ "label": "Sales:Signs",
31
+ "description": null,
32
+ "detail": null,
33
+ "fields": []
34
+ },
35
+ {
36
+ "id": "use:3",
37
+ "label": "Service Income",
38
+ "description": null,
39
+ "detail": null,
40
+ "fields": []
41
+ },
42
+ {
43
+ "id": "use",
44
+ "label": "Use this one",
45
+ "description": null,
46
+ "detail": null,
47
+ "fields": [
48
+ {
49
+ "name": "list_id",
50
+ "type": "record",
51
+ "kind": "account",
52
+ "label": "Search for the one to use",
53
+ "required": true
54
+ }
55
+ ]
56
+ },
57
+ {
58
+ "id": "cancel",
59
+ "label": "Cancel",
60
+ "description": null,
61
+ "detail": null,
62
+ "fields": []
63
+ }
64
+ ]
65
+ },
66
+ "search": {
67
+ "list_id": [
68
+ {
69
+ "value": "8000000A-1700000000",
70
+ "label": "Sales",
71
+ "detail": "Account"
72
+ },
73
+ {
74
+ "value": "8000000B-1700000000",
75
+ "label": "Sales:Signs",
76
+ "detail": "Account"
77
+ },
78
+ {
79
+ "value": "8000000C-1700000000",
80
+ "label": "Service Income",
81
+ "detail": "Account"
82
+ },
83
+ {
84
+ "value": "8000000D-1700000000",
85
+ "label": "Consulting Fees",
86
+ "detail": "Account"
87
+ }
88
+ ]
89
+ }
90
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "title": "An answer QuBe Sync refused (422)",
3
+ "note": "The question again, with why QuBe Sync couldn't take the answer (its errors.detail), from flash[:qube_rails_error].",
4
+ "error": "That answer couldn't be used: a new name is required.",
5
+ "prompt": {
6
+ "id": "ensure / ask_rename",
7
+ "kind": "decision",
8
+ "token": "Zk8tX1yDv6SmE8gG",
9
+ "message": "\"Riverbend Electric\" already belongs to a record of another list (Vendor), and QuickBooks keeps names unique across customers, vendors, employees and other names, and across every type of item. Add this one under another name, or cancel.",
10
+ "data": {
11
+ "kind": "customer",
12
+ "name": "Riverbend Electric",
13
+ "existing": {
14
+ "list": "vendor"
15
+ }
16
+ },
17
+ "details": [
18
+ {
19
+ "label": "Customer",
20
+ "value": "Riverbend Electric"
21
+ },
22
+ {
23
+ "label": "Held by",
24
+ "value": "Vendor"
25
+ }
26
+ ],
27
+ "options": [],
28
+ "choices": [
29
+ {
30
+ "id": "rename",
31
+ "label": "Add it under another name",
32
+ "description": null,
33
+ "detail": null,
34
+ "fields": [
35
+ {
36
+ "name": "name",
37
+ "type": "string",
38
+ "label": "New name",
39
+ "required": true
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "id": "cancel",
45
+ "label": "Cancel",
46
+ "description": "Stop here. Nothing is created.",
47
+ "detail": null,
48
+ "fields": []
49
+ }
50
+ ]
51
+ },
52
+ "search": {}
53
+ }