Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/MODS/PTMatrix/Onboarding.pm
Diff

/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/MODS/PTMatrix/Onboarding.pm

added on local at 2026-07-01 12:34:23

Added
+219
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 32db8f5a065e
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1package MODS::PTMatrix::Onboarding;
2#======================================================================
3# TaskForge -- New-company onboarding
4#
5# seed_sample_data($company_id, $user_id) -- gives a fresh company a
6# pre-populated example project with sample tickets so the dashboard
7# isn't empty. Idempotent: skips if the company already has projects.
8#
9# Sample includes:
10# - 1 sample project ("Welcome Project") with 3 kanban columns
11# - 6 example tasks/tickets demonstrating different work-item types,
12# priorities, statuses, and customer-facing fields
13# - 2 departments (Engineering, Customer Success)
14# - 3 task types (Bug, Feature, Question)
15#======================================================================
16use strict; use warnings;
17
18sub new { return bless {}, shift }
19
20sub seed_sample_data {
21 my ($self, $db, $dbh, $DB, $company_id, $user_id) = @_;
22 return unless $company_id && $user_id;
23
24 # Skip if any project already exists for this company (idempotent).
25 my $r = $db->db_readwrite($dbh, qq~
26 SELECT COUNT(*) AS n FROM `${DB}`.projects WHERE company_id='$company_id'
27 ~, $ENV{SCRIPT_NAME}, __LINE__);
28 return if $r && $r->{n};
29
30 # 1. Departments
31 my %dept;
32 foreach my $d (
33 { name => 'Engineering', color => '#5aa9ff' },
34 { name => 'Customer Success', color => '#10b981' },
35 ) {
36 my $n_q = $d->{name}; $n_q =~ s/'/''/g;
37 my $c_q = $d->{color};
38 my $ins = $db->db_readwrite($dbh, qq~
39 INSERT INTO `${DB}`.departments SET company_id='$company_id', name='$n_q', color='$c_q'
40 ~, $ENV{SCRIPT_NAME}, __LINE__);
41 $dept{ $d->{name} } = ($ins && $ins->{mysql_insertid}) ? $ins->{mysql_insertid} : 0;
42 }
43
44 # 2. Task types — Login::signup_company already seeded Task/Bug/Feature/Chore/Epic.
45 # Read them into %type so the sample-task loop below can reference them by name,
46 # and add 'Question' (used by the sample customer-question ticket) if missing.
47 my %type;
48 my @existing = $db->db_readwrite_multiple($dbh, qq~
49 SELECT id, name FROM `${DB}`.task_types WHERE company_id='$company_id'
50 ~, $ENV{SCRIPT_NAME}, __LINE__);
51 foreach my $row (@existing) {
52 $type{ $row->{name} } = $row->{id};
53 }
54 if (!$type{Question}) {
55 my $ins = $db->db_readwrite($dbh, qq~
56 INSERT INTO `${DB}`.task_types
57 SET company_id='$company_id', name='Question', slug='question', color='#f59e0b'
58 ~, $ENV{SCRIPT_NAME}, __LINE__);
59 $type{Question} = ($ins && $ins->{mysql_insertid}) ? $ins->{mysql_insertid} : 0;
60 }
61
62 # 3. Project
63 my $proj = $db->db_readwrite($dbh, qq~
64 INSERT INTO `${DB}`.projects
65 SET company_id='$company_id',
66 owner_user_id='$user_id',
67 name='Welcome to TaskForge',
68 key_prefix='TF',
69 slug='welcome',
70 color='#5aa9ff',
71 description='Your example project. Explore the sample tickets to see how TaskForge works, then create your first real one.',
72 created_at=NOW()
73 ~, $ENV{SCRIPT_NAME}, __LINE__);
74 my $pid = ($proj && $proj->{mysql_insertid}) ? $proj->{mysql_insertid} : 0;
75 return unless $pid;
76
77 # 4. Kanban columns
78 my @col_ids;
79 foreach my $c (
80 { name => 'To Do', color => '#5aa9ff', pos => 0, done => 0 },
81 { name => 'In Progress', color => '#f59e0b', pos => 1, done => 0 },
82 { name => 'Done', color => '#22c55e', pos => 2, done => 1 },
83 ) {
84 my $n_q = $c->{name}; $n_q =~ s/'/''/g;
85 my $ins = $db->db_readwrite($dbh, qq~
86 INSERT INTO `${DB}`.project_kanban_columns
87 SET project_id='$pid', name='$n_q', color='$c->{color}',
88 position='$c->{pos}', is_done_column='$c->{done}'
89 ~, $ENV{SCRIPT_NAME}, __LINE__);
90 push @col_ids, ($ins && $ins->{mysql_insertid}) ? $ins->{mysql_insertid} : 0;
91 }
92 my ($todo_col, $wip_col, $done_col) = @col_ids;
93
94 # 5. Sample tasks/tickets demonstrating different work-item types.
95 # Detect which optional columns are present so we don't fail on
96 # pre-migration installs.
97 my $has_wit = _col_exists($db, $dbh, $DB, 'tasks', 'work_item_type');
98 my $has_dept = _col_exists($db, $dbh, $DB, 'tasks', 'assigned_department_id');
99
100 my @samples = (
101 {
102 title => 'Welcome! Start here',
103 description => "This is your kanban board. Drag cards between columns to update status.\n\nCustom fields, watchers, attachments, and time tracking all live on each card. Try clicking around!",
104 priority => 'medium',
105 status => 'open',
106 column_id => $todo_col,
107 wit => 'task',
108 type => 'Question',
109 dept => undef,
110 },
111 {
112 title => 'Sample customer ticket: payment failed',
113 description => "Customer emailed: 'My credit card got declined but my bank says it's fine.'\n\nNotice the 'External' badge and customer info — this would normally come in from your support email pipeline.",
114 priority => 'high',
115 status => 'open',
116 column_id => $todo_col,
117 wit => 'ticket',
118 type => 'Bug',
119 dept => 'Customer Success',
120 customer_email => 'alice@example.com',
121 customer_name => 'Alice from Example Co',
122 requester_type => 'external',
123 },
124 {
125 title => 'Sample feature request: dark mode for emails',
126 description => "Customer asks: 'Can the email replies you send respect dark mode like the in-app theme does?'\n\nFeature requests live in the same queue as bugs and tasks — filter to 'Requests' to see just these.",
127 priority => 'low',
128 status => 'open',
129 column_id => $todo_col,
130 wit => 'request',
131 type => 'Feature',
132 dept => 'Engineering',
133 customer_email => 'bob@example.com',
134 customer_name => 'Bob from Example Co',
135 requester_type => 'external',
136 },
137 {
138 title => 'Sample engineering task: refactor the auth module',
139 description => "Internal task. Notice no customer email — this is a regular work item, not a customer-facing ticket. The same kanban handles both.",
140 priority => 'medium',
141 status => 'in_progress',
142 column_id => $wip_col,
143 wit => 'task',
144 type => 'Feature',
145 dept => 'Engineering',
146 },
147 {
148 title => 'Sample bug: kanban scroll bug on iPad',
149 description => "Reported during QA: kanban doesn't scroll smoothly on iPad Pro. Reproduces only in landscape mode.",
150 priority => 'high',
151 status => 'in_progress',
152 column_id => $wip_col,
153 wit => 'bug',
154 type => 'Bug',
155 dept => 'Engineering',
156 },
157 {
158 title => 'Sample completed: shipped the welcome flow',
159 description => "Closed ticket showing how the queue tracks done work. Sort by 'completed' to see your team's velocity.",
160 priority => 'medium',
161 status => 'done',
162 column_id => $done_col,
163 wit => 'task',
164 type => 'Feature',
165 dept => 'Engineering',
166 },
167 );
168
169 my $tn = 0;
170 foreach my $s (@samples) {
171 $tn++;
172 my $t_q = $s->{title}; $t_q =~ s/'/''/g;
173 my $d_q = $s->{description}; $d_q =~ s/'/''/g;
174 my $type_id = $type{ $s->{type} } || 0;
175 my $dept_id = $s->{dept} ? ($dept{ $s->{dept} } || 0) : 0;
176
177 my $extra = '';
178 if ($has_wit) {
179 my $ce = $s->{customer_email} || ''; $ce =~ s/'/''/g;
180 my $cn = $s->{customer_name} || ''; $cn =~ s/'/''/g;
181 my $rt = $s->{requester_type} || 'internal';
182 $extra .= ", work_item_type='$s->{wit}', requester_type='$rt', source='manual'";
183 $extra .= ", customer_email='$ce', customer_name='$cn'" if length $ce;
184 }
185 if ($has_dept && $dept_id) {
186 $extra .= ", assigned_department_id='$dept_id', routed_at=NOW()";
187 }
188 my $type_sql = $type_id ? "'$type_id'" : 'NULL';
189 my $completed = ($s->{status} eq 'done') ? ', completed_at=NOW()' : '';
190
191 $db->db_readwrite($dbh, qq~
192 INSERT INTO `${DB}`.tasks
193 SET company_id='$company_id',
194 project_id='$pid',
195 ticket_number='$tn',
196 title='$t_q',
197 description='$d_q',
198 column_id='$s->{column_id}',
199 position_in_column=0,
200 status='$s->{status}',
201 priority='$s->{priority}',
202 reporter_user_id='$user_id',
203 task_type_id=$type_sql
204 $extra
205 $completed
206 ~, $ENV{SCRIPT_NAME}, __LINE__);
207 }
208}
209
210sub _col_exists {
211 my ($db, $dbh, $DB, $table, $col) = @_;
212 my $r = $db->db_readwrite($dbh, qq~
213 SELECT COUNT(*) AS n FROM information_schema.COLUMNS
214 WHERE TABLE_SCHEMA='$DB' AND TABLE_NAME='$table' AND COLUMN_NAME='$col'
215 ~, $ENV{SCRIPT_NAME}, __LINE__);
216 return ($r && $r->{n}) ? 1 : 0;
217}
218
2191;
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help