Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

393 řádky
17KB

  1. <%
  2. '=======================================================================================================================
  3. ' BUSINESS REPOSITORY
  4. ' Data access for businesses and their logged contacts (interactions). Everything is bound as ADODB parameters
  5. ' through the Db_* helpers (app/lib/lib.Db.asp).
  6. '
  7. ' List filters (Scripting.Dictionary, all optional):
  8. ' q free text: every whitespace-separated term must match name, contact, email, city or phone
  9. ' industry "" = any, "0" = no industry, otherwise an industries.id
  10. ' status "active" (default, hides removed), "all", or new | contacted | follow_up | removed
  11. ' show "" = everyone, "prospects", "customers"
  12. ' contacted "never" = only businesses with no contact yet
  13. ' phone "1" = only businesses with a phone number
  14. ' sort name | city | industry | status | last_contacted | next_action | added (default name)
  15. ' dir asc | desc
  16. '=======================================================================================================================
  17. Class BusinessRepository_Class
  18. Private m_where
  19. Private m_params ' Dictionary index -> Array(adoType, value), in the order of the ? markers in m_where
  20. '---------------------------------------------------------------------------------------------------------------------
  21. ' Lists
  22. '---------------------------------------------------------------------------------------------------------------------
  23. ' One page of the filtered list. Returns Dictionary("rows" -> Dictionary of row Dictionaries, "total" -> count).
  24. Public Function Search(f, page, perPage)
  25. Dim result, rows, total, rs, i, skip, sql
  26. BuildWhere f
  27. Set rs = Run("SELECT COUNT(*) AS n FROM businesses b WHERE " & m_where)
  28. total = CLng(rs("n"))
  29. rs.Close
  30. If page < 1 Then page = 1
  31. skip = (page - 1) * perPage
  32. ' Access has no OFFSET: read TOP (skip + perPage) rows and step past the first `skip` of them.
  33. sql = "SELECT TOP " & CLng(skip + perPage) & " b.id, b.name, b.contact_name, b.phone, b.email, b.website, " & _
  34. "b.city, b.state, b.status, b.is_customer, b.last_contacted_at, b.next_action_date, i.name AS industry_name " & _
  35. "FROM businesses b LEFT JOIN industries i ON i.id = b.industry_id " & _
  36. "WHERE " & m_where & " ORDER BY " & OrderByClause(f)
  37. Set rs = Run(sql)
  38. For i = 1 To skip
  39. If rs.EOF Then Exit For
  40. rs.MoveNext
  41. Next
  42. Set rows = ReadRows(rs)
  43. rs.Close
  44. Set result = CreateObject("Scripting.Dictionary")
  45. Set result("rows") = rows
  46. result("total") = total
  47. Set Search = result
  48. End Function
  49. ' Every business matching the filters (no paging), as a forward-only recordset for streaming to CSV.
  50. Public Function ExportRows(f)
  51. BuildWhere f
  52. Set ExportRows = Run("SELECT b.name, b.contact_name, i.name AS industry_name, b.phone, b.email, b.website, " & _
  53. "b.address1, b.city, b.state, b.zip, b.status, b.is_customer, b.last_contacted_at, b.next_action_date " & _
  54. "FROM businesses b LEFT JOIN industries i ON i.id = b.industry_id " & _
  55. "WHERE " & m_where & " ORDER BY " & OrderByClause(f))
  56. End Function
  57. ' Businesses with a next-contact date on or before throughDate (removed ones excluded), oldest first,
  58. ' each with the details of its most recent logged contact.
  59. Public Function DueThrough(throughDate)
  60. Dim list, rs, i, ids, notesById, k, sql
  61. sql = "SELECT TOP 500 b.id, b.name, b.contact_name, b.phone, b.email, b.city, b.state, b.is_customer, " & _
  62. "b.next_action_date, b.last_contacted_at, i.name AS industry_name, " & _
  63. "(SELECT TOP 1 x.method FROM interactions x WHERE x.business_id = b.id ORDER BY x.contacted_at DESC, x.id DESC) AS last_method, " & _
  64. "(SELECT TOP 1 x.outcome FROM interactions x WHERE x.business_id = b.id ORDER BY x.contacted_at DESC, x.id DESC) AS last_outcome, " & _
  65. "(SELECT TOP 1 x.id FROM interactions x WHERE x.business_id = b.id ORDER BY x.contacted_at DESC, x.id DESC) AS last_interaction_id " & _
  66. "FROM businesses b LEFT JOIN industries i ON i.id = b.industry_id " & _
  67. "WHERE b.next_action_date IS NOT NULL AND b.next_action_date <= ? AND b.status <> 'removed' " & _
  68. "ORDER BY b.next_action_date ASC, b.name ASC"
  69. Set rs = Db_Query(sql, Array(Db_PD(throughDate)))
  70. Set list = ReadRows(rs)
  71. rs.Close
  72. ' Notes are a memo field, which Access won't return from a TOP 1 subquery, so fetch them by interaction id.
  73. ids = ""
  74. For i = 0 To list.Count - 1
  75. If Not IsNull(list(i)("last_interaction_id")) Then
  76. If Len(ids) > 0 Then ids = ids & ","
  77. ids = ids & CLng(list(i)("last_interaction_id"))
  78. End If
  79. Next
  80. Set notesById = CreateObject("Scripting.Dictionary")
  81. If Len(ids) > 0 Then
  82. Set rs = Db_Query("SELECT id, notes FROM interactions WHERE id IN (" & ids & ")", Array())
  83. Do While Not rs.EOF
  84. notesById(CStr(rs(0))) = rs(1) & ""
  85. rs.MoveNext
  86. Loop
  87. rs.Close
  88. End If
  89. For i = 0 To list.Count - 1
  90. list(i)("last_notes") = ""
  91. If Not IsNull(list(i)("last_interaction_id")) Then
  92. k = CStr(list(i)("last_interaction_id"))
  93. If notesById.Exists(k) Then list(i)("last_notes") = notesById(k)
  94. End If
  95. Next
  96. Set DueThrough = list
  97. End Function
  98. ' id -> name, for dropdowns
  99. Public Function Industries()
  100. Dim list, rs
  101. Set list = CreateObject("Scripting.Dictionary")
  102. Set rs = Db_Query("SELECT id, name FROM industries ORDER BY name", Array())
  103. Do While Not rs.EOF
  104. list.Add CStr(rs(0)), rs(1) & ""
  105. rs.MoveNext
  106. Loop
  107. rs.Close
  108. Set Industries = list
  109. End Function
  110. '---------------------------------------------------------------------------------------------------------------------
  111. ' One business and its history
  112. '---------------------------------------------------------------------------------------------------------------------
  113. ' All columns plus industry_name, or Nothing.
  114. Public Function Find(id)
  115. Dim rs, rows
  116. Set rs = Db_Query("SELECT b.*, i.name AS industry_name FROM businesses b LEFT JOIN industries i ON i.id = b.industry_id WHERE b.id = ?", Array(Db_PI(id)))
  117. Set rows = ReadRows(rs)
  118. rs.Close
  119. If rows.Count = 0 Then Set Find = Nothing Else Set Find = rows(0)
  120. End Function
  121. ' Logged contacts for a business, newest first.
  122. Public Function Interactions(businessId)
  123. Dim rs
  124. Set rs = Db_Query("SELECT id, method, outcome, notes, contacted_at, follow_up_date FROM interactions WHERE business_id = ? ORDER BY contacted_at DESC, id DESC", Array(Db_PI(businessId)))
  125. Set Interactions = ReadRows(rs)
  126. rs.Close
  127. End Function
  128. ' Another business (not excludeId) with the same phone digits or the same email, or Nothing.
  129. ' Returns Dictionary(id, name, matched) where matched is "phone" or "email".
  130. Public Function FindDuplicate(excludeId, phoneDigits, email)
  131. Dim d
  132. Set FindDuplicate = Nothing
  133. If Len(phoneDigits) > 0 Then
  134. Set d = FirstMatch("SELECT TOP 1 id, name FROM businesses WHERE id <> ? AND phone_digits = ?", Array(Db_PI(excludeId), Db_PS(phoneDigits)), "phone")
  135. If Not d Is Nothing Then
  136. Set FindDuplicate = d
  137. Exit Function
  138. End If
  139. End If
  140. If Len(email) > 0 Then
  141. Set FindDuplicate = FirstMatch("SELECT TOP 1 id, name FROM businesses WHERE id <> ? AND email = ?", Array(Db_PI(excludeId), Db_PS(email)), "email")
  142. End If
  143. End Function
  144. ' Returns the industry id for a name, creating the industry when it does not exist. Null for a blank name.
  145. Public Function FindOrCreateIndustry(name)
  146. Dim rs
  147. name = Left(Trim(name), 100)
  148. If Len(name) = 0 Then
  149. FindOrCreateIndustry = Null
  150. Exit Function
  151. End If
  152. Set rs = Db_Query("SELECT id FROM industries WHERE name = ?", Array(Db_PS(name)))
  153. If Not rs.EOF Then
  154. FindOrCreateIndustry = CLng(rs(0))
  155. rs.Close
  156. Exit Function
  157. End If
  158. rs.Close
  159. Db_Exec "INSERT INTO industries (name) VALUES (?)", Array(Db_PS(name))
  160. Set rs = Db_Query("SELECT @@IDENTITY", Array())
  161. FindOrCreateIndustry = CLng(rs(0))
  162. rs.Close
  163. End Function
  164. '---------------------------------------------------------------------------------------------------------------------
  165. ' Changes
  166. '---------------------------------------------------------------------------------------------------------------------
  167. Public Sub Update(id, d)
  168. Db_Exec "UPDATE businesses SET name = ?, contact_name = ?, industry_id = ?, phone = ?, phone_digits = ?, email = ?, " & _
  169. "website = ?, address1 = ?, city = ?, state = ?, zip = ?, notes = ?, is_customer = ?, first_order_date = ? WHERE id = ?", _
  170. Array(Db_PS(d("name")), Db_PS(d("contact_name")), Db_PI(d("industry_id")), Db_PS(d("phone")), Db_PS(d("phone_digits")), Db_PS(d("email")), _
  171. Db_PS(d("website")), Db_PS(d("address1")), Db_PS(d("city")), Db_PS(d("state")), Db_PS(d("zip")), Db_PS(d("notes")), _
  172. Db_PI(d("is_customer")), Db_PD(d("first_order_date")), Db_PI(id))
  173. End Sub
  174. ' Logs a touch and updates the business in one transaction:
  175. ' removing -> status removed, no next action
  176. ' follow-up date -> status follow_up, next_action_date = follow-up date
  177. ' otherwise -> status contacted, next_action_date cleared
  178. Public Sub LogInteraction(id, method, outcome, notes, contactedAt, followUp, removing, removeReason)
  179. Dim conn, b, status, nextAction, lastContacted, remReason, remAt, msg
  180. Set conn = DAL().Connection
  181. Set b = Find(id)
  182. lastContacted = contactedAt
  183. If Not IsNull(b("last_contacted_at")) Then
  184. If CDate(b("last_contacted_at")) > contactedAt Then lastContacted = CDate(b("last_contacted_at"))
  185. End If
  186. remReason = Null : remAt = Null : nextAction = Null
  187. If removing Then
  188. status = "removed"
  189. remReason = removeReason
  190. remAt = Now()
  191. ElseIf Not IsNull(followUp) Then
  192. status = "follow_up"
  193. nextAction = followUp
  194. Else
  195. status = "contacted"
  196. End If
  197. conn.BeginTrans
  198. On Error Resume Next
  199. Db_Exec "INSERT INTO interactions (business_id, method, outcome, notes, contacted_at, follow_up_date, created_at) VALUES (?,?,?,?,?,?,Now())", _
  200. Array(Db_PI(id), Db_PS(method), Db_PS(outcome), Db_PS(notes), Db_PD(contactedAt), Db_PD(followUp))
  201. If Err.Number = 0 Then
  202. Db_Exec "UPDATE businesses SET last_contacted_at = ?, status = ?, next_action_date = ?, removed_reason = ?, removed_at = ? WHERE id = ?", _
  203. Array(Db_PD(lastContacted), Db_PS(status), Db_PD(nextAction), Db_PS(remReason), Db_PD(remAt), Db_PI(id))
  204. End If
  205. If Err.Number <> 0 Then
  206. msg = Err.Description
  207. Err.Clear
  208. conn.RollbackTrans
  209. On Error GoTo 0
  210. Err.Raise 5, "BusinessRepository.LogInteraction", msg
  211. End If
  212. conn.CommitTrans
  213. On Error GoTo 0
  214. End Sub
  215. Public Sub Remove(id, reason)
  216. Db_Exec "UPDATE businesses SET status = 'removed', next_action_date = NULL, removed_reason = ?, removed_at = Now() WHERE id = ?", _
  217. Array(Db_PS(reason), Db_PI(id))
  218. End Sub
  219. Public Sub Restore(id)
  220. Db_Exec "UPDATE businesses SET status = IIf(last_contacted_at IS NULL, 'new', 'contacted'), removed_reason = NULL, removed_at = NULL WHERE id = ?", _
  221. Array(Db_PI(id))
  222. End Sub
  223. '---------------------------------------------------------------------------------------------------------------------
  224. ' Internals
  225. '---------------------------------------------------------------------------------------------------------------------
  226. ' Reads every remaining row of the recordset into a Dictionary (0-based index -> Dictionary of column -> value).
  227. Private Function ReadRows(rs)
  228. Dim list, d, i
  229. Set list = CreateObject("Scripting.Dictionary")
  230. Do While Not rs.EOF
  231. Set d = CreateObject("Scripting.Dictionary")
  232. For i = 0 To rs.Fields.Count - 1
  233. d(rs.Fields(i).Name) = rs.Fields(i).Value
  234. Next
  235. list.Add list.Count, d
  236. rs.MoveNext
  237. Loop
  238. Set ReadRows = list
  239. End Function
  240. ' Runs a query expected to return (id, name) and wraps the first row as Dictionary(id, name, matched), or Nothing.
  241. Private Function FirstMatch(sql, params, matched)
  242. Dim rs, d
  243. Set FirstMatch = Nothing
  244. Set rs = Db_Query(sql, params)
  245. If Not rs.EOF Then
  246. Set d = CreateObject("Scripting.Dictionary")
  247. d("id") = CLng(rs(0)) : d("name") = rs(1) & "" : d("matched") = matched
  248. Set FirstMatch = d
  249. End If
  250. rs.Close
  251. End Function
  252. ' Executes sql with the parameters collected by BuildWhere.
  253. Private Function Run(sql)
  254. Dim params(), i
  255. If m_params.Count = 0 Then
  256. Set Run = Db_Query(sql, Array())
  257. Exit Function
  258. End If
  259. ReDim params(m_params.Count - 1)
  260. For i = 0 To m_params.Count - 1
  261. params(i) = m_params(i)
  262. Next
  263. Set Run = Db_Query(sql, params)
  264. End Function
  265. Private Sub AddParam(typ, v)
  266. m_params.Add m_params.Count, Array(typ, v)
  267. End Sub
  268. Private Sub BuildWhere(f)
  269. Dim terms, t, d, q, n, frag, ind, st
  270. m_where = "1=1"
  271. Set m_params = CreateObject("Scripting.Dictionary")
  272. q = Trim(Val_(f, "q"))
  273. If Len(q) > 0 Then
  274. terms = Split(q, " ")
  275. n = 0
  276. For Each t In terms
  277. If Len(t) > 0 And n < 5 Then
  278. n = n + 1
  279. t = Left(t, 50)
  280. d = ImportFile_PhoneDigitsLoose(t)
  281. frag = "(b.name LIKE ? OR b.contact_name LIKE ? OR b.email LIKE ? OR b.city LIKE ?"
  282. AddParam 202, "%" & Like_(t) & "%"
  283. AddParam 202, "%" & Like_(t) & "%"
  284. AddParam 202, "%" & Like_(t) & "%"
  285. AddParam 202, "%" & Like_(t) & "%"
  286. If Len(d) >= 3 Then
  287. frag = frag & " OR b.phone_digits LIKE ?"
  288. AddParam 202, "%" & d & "%"
  289. End If
  290. m_where = m_where & " AND " & frag & ")"
  291. End If
  292. Next
  293. End If
  294. ind = Val_(f, "industry")
  295. If ind = "0" Then
  296. m_where = m_where & " AND b.industry_id IS NULL"
  297. ElseIf Valid_Id(ind) Then
  298. m_where = m_where & " AND b.industry_id = ?"
  299. AddParam 3, CLng(ind)
  300. End If
  301. st = Val_(f, "status")
  302. Select Case st
  303. Case "all"
  304. Case "new", "contacted", "follow_up", "removed"
  305. m_where = m_where & " AND b.status = ?"
  306. AddParam 202, st
  307. Case Else
  308. m_where = m_where & " AND b.status <> 'removed'"
  309. End Select
  310. Select Case Val_(f, "show")
  311. Case "prospects"
  312. m_where = m_where & " AND (b.is_customer = 0 OR b.is_customer IS NULL)"
  313. Case "customers"
  314. m_where = m_where & " AND b.is_customer = 1"
  315. End Select
  316. If Val_(f, "contacted") = "never" Then m_where = m_where & " AND b.last_contacted_at IS NULL"
  317. If Val_(f, "phone") = "1" Then m_where = m_where & " AND b.phone_digits IS NOT NULL"
  318. End Sub
  319. Private Function OrderByClause(f)
  320. Dim col, dirSql
  321. Select Case Val_(f, "sort")
  322. Case "city": col = "b.city"
  323. Case "industry": col = "i.name"
  324. Case "status": col = "b.status"
  325. Case "last_contacted": col = "b.last_contacted_at"
  326. Case "next_action": col = "b.next_action_date"
  327. Case "added": col = "b.created_at"
  328. Case Else: col = "b.name"
  329. End Select
  330. If LCase(Val_(f, "dir")) = "desc" Then dirSql = "DESC" Else dirSql = "ASC"
  331. OrderByClause = col & " " & dirSql & ", b.id " & dirSql
  332. End Function
  333. Private Function Val_(f, key)
  334. If f.Exists(key) Then Val_ = CStr(f(key)) Else Val_ = ""
  335. End Function
  336. ' Escape LIKE wildcards so user input is matched literally.
  337. Private Function Like_(s)
  338. s = Replace(s, "[", "[[]")
  339. s = Replace(s, "%", "[%]")
  340. s = Replace(s, "_", "[_]")
  341. s = Replace(s, "*", "[*]")
  342. s = Replace(s, "?", "[?]")
  343. Like_ = s
  344. End Function
  345. End Class
  346. Dim BusinessRepository_Class__Singleton
  347. Function BusinessRepository()
  348. If IsEmpty(BusinessRepository_Class__Singleton) Then
  349. Set BusinessRepository_Class__Singleton = New BusinessRepository_Class
  350. End If
  351. Set BusinessRepository = BusinessRepository_Class__Singleton
  352. End Function
  353. %>

Powered by TurnKey Linux.