New /business/new page (linked from the Businesses list) and POST /business/create. Same fields as the edit form, duplicate phone/email warning with "Save anyway", status "new", source "Added manually". Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>master
| @@ -104,6 +104,109 @@ Class BusinessesController_Class | |||
| <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' GET /business/new : blank form to enter a business by hand ("new" is reserved in VBScript, hence the action name) | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub add() | |||
| Dim fv | |||
| Set fv = CreateObject("Scripting.Dictionary") | |||
| Set fv("_area_ids") = CreateObject("Scripting.Dictionary") | |||
| RenderAdd Nothing, fv | |||
| End Sub | |||
| ' dup is a matching other business (or Nothing); fv holds the form values to show. | |||
| Private Sub RenderAdd(dup, fv) | |||
| Dim industries, allAreas, areaSel, csrf, ix, indKey2 | |||
| Set industries = BusinessRepository().Industries() | |||
| Set allAreas = AreaRepository().All() | |||
| Set areaSel = fv("_area_ids") | |||
| csrf = Csrf_Token("business") | |||
| m_title = "Add business" | |||
| %> | |||
| <!--#include file="../views/Businesses/add.asp" --> | |||
| <% | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' POST /business/create | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| Public Sub create() | |||
| Dim d, email, phone, indName, indSel, areaIds, areaNew, ai, areaId, areaName, dupMatch, fv, k, newId | |||
| If Not Csrf_Valid("business", F("csrf")) Then | |||
| Flash().AddError "Invalid form state. Please try again." | |||
| Response.Redirect "/business/new" | |||
| End If | |||
| Set d = CreateObject("Scripting.Dictionary") | |||
| d("name") = Left(Trim(F("name")), 255) | |||
| email = LCase(Left(Trim(F("email")), 255)) | |||
| phone = Left(Trim(F("phone")), 40) | |||
| d("contact_name") = Left(Trim(F("contact_name")), 150) | |||
| d("phone") = phone | |||
| d("phone_digits") = ImportFile_PhoneDigits(phone) | |||
| d("email") = email | |||
| d("website") = Left(Trim(F("website")), 255) | |||
| d("address1") = Left(Trim(F("address1")), 255) | |||
| d("city") = Left(Trim(F("city")), 100) | |||
| d("state") = Left(Trim(F("state")), 50) | |||
| d("zip") = Left(Trim(F("zip")), 20) | |||
| d("notes") = Left(F("notes"), 10000) | |||
| If F("is_customer") = "1" Then d("is_customer") = 1 Else d("is_customer") = 0 | |||
| d("first_order_date") = Date_Parse(F("first_order_date")) | |||
| indName = Trim(F("industry_new")) | |||
| indSel = F("industry_id") | |||
| If Valid_Id(indSel) Then d("industry_id") = CLng(indSel) Else d("industry_id") = Null | |||
| Set areaIds = CreateObject("Scripting.Dictionary") | |||
| For ai = 1 To Request.Form("area_ids").Count | |||
| If Valid_Id(Request.Form("area_ids")(ai)) Then areaIds(CStr(CLng(Request.Form("area_ids")(ai)))) = True | |||
| Next | |||
| areaNew = Left(Trim(F("area_new")), 500) | |||
| Set fv = CreateObject("Scripting.Dictionary") | |||
| For Each k In d.Keys | |||
| fv(k) = d(k) | |||
| Next | |||
| fv("_industry_new") = indName | |||
| fv("_area_new") = areaNew | |||
| Set fv("_area_ids") = areaIds | |||
| If Len(d("name")) = 0 Then | |||
| Flash().AddError "Business name is required." | |||
| RenderAdd Nothing, fv | |||
| Exit Sub | |||
| End If | |||
| If Len(email) > 0 And InStr(email, "@") = 0 Then | |||
| Flash().AddError "That email address doesn't look right, so nothing was saved." | |||
| RenderAdd Nothing, fv | |||
| Exit Sub | |||
| End If | |||
| ' Warn when the phone/email already belongs to another business (unless "Save anyway" was pressed). | |||
| If F("allow_duplicate") <> "1" Then | |||
| Set dupMatch = BusinessRepository().FindDuplicate(0, d("phone_digits"), d("email")) | |||
| If Not dupMatch Is Nothing Then | |||
| RenderAdd dupMatch, fv | |||
| Exit Sub | |||
| End If | |||
| End If | |||
| If Len(indName) > 0 Then d("industry_id") = BusinessRepository().FindOrCreateIndustry(indName) | |||
| newId = BusinessRepository().Create(d) | |||
| For Each areaName In Split(areaNew, ",") | |||
| areaId = AreaRepository().FindOrCreate(areaName) | |||
| If Not IsNull(areaId) Then areaIds(CStr(areaId)) = True | |||
| Next | |||
| AreaRepository().SetForBusiness newId, areaIds | |||
| Flash().Success = "Added " & d("name") & "." | |||
| Response.Redirect "/businesses/" & newId | |||
| End Sub | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| ' POST /businesses/:id/update : edit details | |||
| '------------------------------------------------------------------------------------------------------------------- | |||
| @@ -217,6 +217,19 @@ Class BusinessRepository_Class | |||
| ' Changes | |||
| '--------------------------------------------------------------------------------------------------------------------- | |||
| ' Inserts a business entered by hand (status "new") and returns its id. d holds the same keys as Update. | |||
| Public Function Create(d) | |||
| Dim rs | |||
| Db_Exec "INSERT INTO businesses (name, contact_name, industry_id, phone, phone_digits, email, website, address1, city, state, zip, " & _ | |||
| "source, status, is_customer, first_order_date, notes, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,Now())", _ | |||
| 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")), _ | |||
| Db_PS(d("website")), Db_PS(d("address1")), Db_PS(d("city")), Db_PS(d("state")), Db_PS(d("zip")), _ | |||
| Db_PS("Added manually"), Db_PS("new"), Db_PI(d("is_customer")), Db_PD(d("first_order_date")), Db_PS(d("notes"))) | |||
| Set rs = Db_Query("SELECT @@IDENTITY", Array()) | |||
| Create = CLng(rs(0)) | |||
| rs.Close | |||
| End Function | |||
| Public Sub Update(id, d) | |||
| Db_Exec "UPDATE businesses SET name = ?, contact_name = ?, industry_id = ?, phone = ?, phone_digits = ?, email = ?, " & _ | |||
| "website = ?, address1 = ?, city = ?, state = ?, zip = ?, notes = ?, is_customer = ?, first_order_date = ? WHERE id = ?", _ | |||
| @@ -0,0 +1,112 @@ | |||
| <% Flash().ShowErrorsIfPresent : Flash().ShowSuccessIfPresent %> | |||
| <nav aria-label="breadcrumb"> | |||
| <ol class="breadcrumb small mb-2"> | |||
| <li class="breadcrumb-item"><a href="/businesses">Businesses</a></li> | |||
| <li class="breadcrumb-item active" aria-current="page">Add business</li> | |||
| </ol> | |||
| </nav> | |||
| <h1 class="h3 mb-3">Add business</h1> | |||
| <div class="row"> | |||
| <div class="col-lg-7"> | |||
| <% If Not dup Is Nothing Then %> | |||
| <div class="alert alert-warning small"> | |||
| <i class="bi bi-exclamation-triangle"></i> | |||
| Nothing was saved yet. The <%= dup("matched") %> you entered is already used by | |||
| <a href="/businesses/<%= dup("id") %>" target="_blank" rel="noopener"><%= H(dup("name")) %></a>. | |||
| Fix it, or press <strong>Save anyway</strong> to add it as a second business. | |||
| </div> | |||
| <% End If %> | |||
| <div class="card shadow-sm mb-3"> | |||
| <div class="card-body"> | |||
| <form method="post" action="/business/create"> | |||
| <input type="hidden" name="csrf" value="<%= H(csrf) %>" /> | |||
| <% If Not dup Is Nothing Then %><input type="hidden" name="allow_duplicate" value="1" /><% End If %> | |||
| <div class="mb-2"> | |||
| <label class="form-label small mb-1" for="f_name">Business name</label> | |||
| <input type="text" class="form-control form-control-sm" id="f_name" name="name" value="<%= H(fv("name")) %>" maxlength="255" required autofocus /> | |||
| </div> | |||
| <div class="mb-2"> | |||
| <label class="form-label small mb-1" for="f_contact">Contact</label> | |||
| <input type="text" class="form-control form-control-sm" id="f_contact" name="contact_name" value="<%= H(fv("contact_name")) %>" maxlength="150" /> | |||
| </div> | |||
| <div class="row g-2 mb-2"> | |||
| <div class="col-6"> | |||
| <label class="form-label small mb-1" for="f_phone">Phone</label> | |||
| <input type="text" class="form-control form-control-sm" id="f_phone" name="phone" value="<%= H(fv("phone")) %>" maxlength="40" /> | |||
| </div> | |||
| <div class="col-6"> | |||
| <label class="form-label small mb-1" for="f_email">Email</label> | |||
| <input type="text" class="form-control form-control-sm" id="f_email" name="email" value="<%= H(fv("email")) %>" maxlength="255" /> | |||
| </div> | |||
| </div> | |||
| <div class="mb-2"> | |||
| <label class="form-label small mb-1" for="f_web">Website</label> | |||
| <input type="text" class="form-control form-control-sm" id="f_web" name="website" value="<%= H(fv("website")) %>" maxlength="255" /> | |||
| </div> | |||
| <div class="mb-2"> | |||
| <label class="form-label small mb-1" for="f_addr">Address</label> | |||
| <input type="text" class="form-control form-control-sm" id="f_addr" name="address1" value="<%= H(fv("address1")) %>" maxlength="255" /> | |||
| </div> | |||
| <div class="row g-2 mb-2"> | |||
| <div class="col-5"><label class="form-label small mb-1" for="f_city">City</label><input type="text" class="form-control form-control-sm" id="f_city" name="city" value="<%= H(fv("city")) %>" maxlength="100" /></div> | |||
| <div class="col-3"><label class="form-label small mb-1" for="f_state">State</label><input type="text" class="form-control form-control-sm" id="f_state" name="state" value="<%= H(fv("state")) %>" maxlength="50" /></div> | |||
| <div class="col-4"><label class="form-label small mb-1" for="f_zip">Zip</label><input type="text" class="form-control form-control-sm" id="f_zip" name="zip" value="<%= H(fv("zip")) %>" maxlength="20" /></div> | |||
| </div> | |||
| <div class="row g-2 mb-2"> | |||
| <div class="col-6"> | |||
| <label class="form-label small mb-1" for="f_ind">Industry</label> | |||
| <select class="form-select form-select-sm" id="f_ind" name="industry_id"> | |||
| <option value="">(none)</option> | |||
| <% For Each indKey2 In industries.Keys %> | |||
| <option value="<%= indKey2 %>"<% If CStr(fv("industry_id") & "") = indKey2 Then Response.Write " selected" %>><%= H(industries(indKey2)) %></option> | |||
| <% Next %> | |||
| </select> | |||
| </div> | |||
| <div class="col-6"> | |||
| <label class="form-label small mb-1" for="f_indnew">…or add new</label> | |||
| <input type="text" class="form-control form-control-sm" id="f_indnew" name="industry_new" maxlength="100" value="<% If fv.Exists("_industry_new") Then Response.Write H(fv("_industry_new")) %>" /> | |||
| </div> | |||
| </div> | |||
| <fieldset class="mb-2"> | |||
| <legend class="form-label small mb-1">Areas served</legend> | |||
| <% If allAreas.Count > 0 Then %> | |||
| <div class="border rounded px-2 py-1 mb-1" style="max-height:9rem;overflow-y:auto"> | |||
| <% For ix = 0 To allAreas.Count - 1 %> | |||
| <div class="form-check form-check-inline"> | |||
| <input class="form-check-input" type="checkbox" id="f_area_<%= allAreas(ix)("id") %>" name="area_ids" value="<%= allAreas(ix)("id") %>"<% If areaSel.Exists(CStr(allAreas(ix)("id"))) Then Response.Write " checked" %> /> | |||
| <label class="form-check-label small" for="f_area_<%= allAreas(ix)("id") %>"><%= H(allAreas(ix)("name")) %></label> | |||
| </div> | |||
| <% Next %> | |||
| </div> | |||
| <% End If %> | |||
| <label class="visually-hidden" for="f_areanew">Add new areas</label> | |||
| <input type="text" class="form-control form-control-sm" id="f_areanew" name="area_new" maxlength="500" placeholder="<% If allAreas.Count > 0 Then Response.Write "…or add new, comma-separated" Else Response.Write "e.g. Dallas, Fort Worth" %>" value="<% If fv.Exists("_area_new") Then Response.Write H(fv("_area_new")) %>" /> | |||
| </fieldset> | |||
| <div class="row g-2 mb-2 align-items-end"> | |||
| <div class="col-6"> | |||
| <div class="form-check"> | |||
| <input class="form-check-input" type="checkbox" id="f_cust" name="is_customer" value="1"<% If fv("is_customer") & "" = "1" Then Response.Write " checked" %> /> | |||
| <label class="form-check-label small" for="f_cust">Previous customer</label> | |||
| </div> | |||
| </div> | |||
| <div class="col-6"> | |||
| <label class="form-label small mb-1" for="f_first">First order</label> | |||
| <input type="date" class="form-control form-control-sm" id="f_first" name="first_order_date" value="<%= Date_Iso(fv("first_order_date")) %>" /> | |||
| </div> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label class="form-label small mb-1" for="f_notes">Notes</label> | |||
| <textarea class="form-control form-control-sm" id="f_notes" name="notes" rows="4" maxlength="10000"><%= H(fv("notes")) %></textarea> | |||
| </div> | |||
| <button type="submit" class="btn <% If dup Is Nothing Then Response.Write "btn-primary" Else Response.Write "btn-warning" %> btn-sm"><% If dup Is Nothing Then Response.Write "Add business" Else Response.Write "Save anyway" %></button> | |||
| <a href="/businesses" class="btn btn-outline-secondary btn-sm">Cancel</a> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -3,6 +3,7 @@ | |||
| <div class="d-flex justify-content-between align-items-center mb-3"> | |||
| <h1 class="h3 mb-0">Businesses</h1> | |||
| <div class="d-flex gap-2"> | |||
| <a href="/business/new" class="btn btn-primary btn-sm"><i class="bi bi-plus-lg"></i> Add business</a> | |||
| <a href="/export/businesses<%= H(Businesses_QS(f, 1, sort, dir)) %>" class="btn btn-outline-success btn-sm" title="Download everything matching the current search and filters as a spreadsheet (CSV)"><i class="bi bi-download"></i> Export<% If total > 0 Then Response.Write " " & FormatNumber(total, 0) %></a> | |||
| <a href="/import" class="btn btn-outline-primary btn-sm"><i class="bi bi-upload"></i> Import</a> | |||
| </div> | |||
| @@ -15,6 +15,8 @@ | |||
| ' Businesses | |||
| router.AddRoute "GET", "/businesses", "BusinessesController", "Index" | |||
| router.AddRoute "GET", "/business/new", "BusinessesController", "Add" | |||
| router.AddRoute "POST", "/business/create", "BusinessesController", "Create" | |||
| router.AddRoute "GET", "/businesses/:id", "BusinessesController", "Show" | |||
| router.AddRoute "POST", "/businesses/:id/update", "BusinessesController", "Update" | |||
| router.AddRoute "POST", "/businesses/:id/log", "BusinessesController", "Log" | |||
Powered by TurnKey Linux.