feat!: P1 platform upgrade, crm_ names, permissions and schema #8
No files matched your search
+52
-39
@@ -1,47 +1,60 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
qqq-orb: kingsrook/qqq-orb@0.3.8
|
||||
qqq-orb: kingsrook/qqq-orb@0.6.9
|
||||
|
||||
commands:
|
||||
configure-package-install:
|
||||
steps:
|
||||
- run:
|
||||
name: Keep package installation unattended
|
||||
command: |
|
||||
# These build machines are discarded after the job.
|
||||
sudo install -d /etc/needrestart/conf.d
|
||||
printf '%s\n' '$nrconf{restart} = "l";' | sudo tee /etc/needrestart/conf.d/99-qqq-ci.conf >/dev/null
|
||||
|
||||
workflows:
|
||||
test_only:
|
||||
jobs:
|
||||
- qqq-orb/mvn_test_only:
|
||||
pre-steps:
|
||||
- configure-package-install
|
||||
context: [qqq-maven-registry-credentials]
|
||||
filters:
|
||||
branches:
|
||||
ignore: /(develop|release\/.*)/
|
||||
|
||||
test_only:
|
||||
jobs:
|
||||
- qqq-orb/mvn_test_only:
|
||||
context: [ qqq-maven-registry-credentials ]
|
||||
filters:
|
||||
branches:
|
||||
ignore: /(develop|main|release/.*|integration.*)/
|
||||
tags:
|
||||
only: []
|
||||
publish_snapshot:
|
||||
jobs:
|
||||
- qqq-orb/mvn_publish:
|
||||
pre-steps:
|
||||
- configure-package-install
|
||||
context: [qqq-maven-registry-credentials]
|
||||
branch_type: snapshot
|
||||
filters:
|
||||
branches:
|
||||
only: [develop]
|
||||
|
||||
publish_snapshot:
|
||||
jobs:
|
||||
- qqq-orb/mvn_publish:
|
||||
context: [ qqq-maven-registry-credentials ]
|
||||
branch_type: snapshot
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
publish_release_candidate:
|
||||
jobs:
|
||||
- qqq-orb/mvn_publish:
|
||||
pre-steps:
|
||||
- configure-package-install
|
||||
context: [qqq-maven-registry-credentials]
|
||||
branch_type: release_candidate
|
||||
filters:
|
||||
branches:
|
||||
only: [/release\/.*/]
|
||||
|
||||
publish_release_candidate:
|
||||
jobs:
|
||||
- qqq-orb/mvn_publish:
|
||||
context: [ qqq-maven-registry-credentials ]
|
||||
branch_type: release_candidate
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
|
||||
publish_release:
|
||||
jobs:
|
||||
- qqq-orb/mvn_publish:
|
||||
context: [ qqq-maven-registry-credentials ]
|
||||
branch_type: release
|
||||
filters:
|
||||
branches:
|
||||
only: [ main ]
|
||||
tags:
|
||||
only: [ /^v\d+\.\d+\.\d+$/ ]
|
||||
publish_release:
|
||||
jobs:
|
||||
- qqq-orb/mvn_publish:
|
||||
pre-steps:
|
||||
- configure-package-install
|
||||
context: [qqq-maven-registry-credentials]
|
||||
branch_type: release
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: [/^v\d+\.\d+\.\d+$/]
|
||||
@@ -0,0 +1,2 @@
|
||||
mvnw text eol=lf
|
||||
mvnw.cmd text eol=crlf
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
wrapperVersion=3.3.4
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
|
||||
@@ -0,0 +1,15 @@
|
||||
# QBit CRM
|
||||
|
||||
CRM metadata, processes and widgets for QQQ. P1 upgrades the platform; later phases are defined in the 0.2.0 design.
|
||||
|
||||
## Build
|
||||
|
||||
Use Java 21 and the Maven wrapper: `./mvnw -q verify` runs checkstyle, tests and JaCoCo (70% instructions, 90% classes). Parent: `qbit-build-parent:2.1.2`; default-active `qqq-snapshot` BOM: `4.1.0-RC.6`. Architecture guardrails and strict UI API routes stay enabled.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Entry point: `CrmQBitProducer`, with package-discovered metadata producers.
|
||||
- Table, join, widget and process names use `crm_` snake_case. Database columns use snake_case; Java fields stay camelCase.
|
||||
- Allman braces, 3-space indentation, `return (x);`, fluent setters and Javadoc flower-box banners.
|
||||
- New source files carry the Apache 2.0 license header.
|
||||
- Keep changes scoped and run verify before committing. Do not push without authorization.
|
||||
@@ -0,0 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased / 0.2.0
|
||||
|
||||
- Upgrade to Java 21, QQQ 4.1.0-RC.6 and qbit-build-parent 2.1.2.
|
||||
- Add Maven wrapper, CI configuration, architecture guardrails and coverage gates.
|
||||
- Rename tables, joins, widgets and CRM processes to `crm_` snake_case, with inferred snake_case SQL columns.
|
||||
- Remove bespoke tag, attachment and audit tables, bulk tag processes and unused namespace/prefix configuration.
|
||||
- Use core FIELD-level auditing on contacts, companies, deals and activities.
|
||||
- Apply CRM, management and admin permission rules and source-QBit metadata, including an RC.6 widget provenance adapter.
|
||||
- Ship PostgreSQL DDL, 33 Liquibase changesets, and H2 persistence and schema tests. 0.2.0 is a fresh install; there is no upgrade path from 0.1.0.
|
||||
- Correct installation instructions and document the existing P1 behavior and remaining limitations.
|
||||
@@ -1,485 +1,140 @@
|
||||
# QBit: CRM
|
||||
# QBit CRM
|
||||
|
||||
[](https://github.com/QRun-IO/qbit-crm)
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
[](https://adoptium.net/)
|
||||
**0.2.0-SNAPSHOT — unreleased, P1 Platform.** CRM tables, processes and widgets for QQQ applications. This phase upgrades the build, names, permissions, audit integration and database schema. It does not complete the later 0.2.0 product phases.
|
||||
|
||||
> **Customer Relationship Management for QQQ Applications - Activity-Centric, Pipeline-Driven**
|
||||
## Build and verification
|
||||
|
||||
This QBit provides a complete CRM system for QQQ applications, covering contact and company management, deal pipeline tracking, activity logging, email tracking, product catalog, lead scoring, assignment automation, website form capture, mailing list subscriptions, and reporting.
|
||||
Use **Java 21** and the included Maven wrapper (**Maven 3.9.11**). The build uses `com.kingsrook:qbit-build-parent:2.1.2` and a default-active `qqq-snapshot` profile importing `qqq-bom-pom:4.1.0-RC.6`.
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
- **Contact & Company Management**: Full lifecycle tracking with tagging, segmentation, and duplicate detection
|
||||
- **Deal Pipeline**: Configurable multi-pipeline stages with probability weighting, rot/aging detection, and stage-gating rules
|
||||
- **Activity Tracking**: Unified activity table for calls, emails, meetings, notes, and tasks with per-record timelines
|
||||
- **Email Integration**: Tracked email sending, open/click/bounce detection, templates with merge fields
|
||||
- **Email Sequences**: Multi-step automated outreach with configurable send windows and failure handling
|
||||
- **Lead Scoring**: Rule-based scoring with dot-notation field paths and time-decay operators
|
||||
- **Assignment Automation**: Round-robin, least-active, and rule-based auto-assignment
|
||||
- **Website Forms**: Capture web form submissions with UTM tracking and lead conversion
|
||||
- **Mailing Lists**: Newsletter and subscription management independent of sales sequences
|
||||
- **GDPR Compliance**: Consent tracking, right-to-erasure via anonymization (not deletion)
|
||||
- **Multi-Currency**: Exchange rate table with converted amounts for accurate pipeline reporting
|
||||
- **Immutable Audit Log**: Field-level change tracking with anonymization support for GDPR
|
||||
- **14 Dashboard Widgets**: Pipeline summary, forecast, leaderboard, conversion rates, win rate, activity feed, and more
|
||||
|
||||
## Open Source & Full Control
|
||||
|
||||
QBit CRM is 100% open source under Apache 2.0. All data stays in your systems. No external CRM services required.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Design Principles
|
||||
|
||||
1. **Activity-centric**: A unified `crm_activity` table is the heart of the system. Every interaction (calls, emails, meetings, notes, tasks) is an activity linked to contacts, companies, and deals. One table to query, one timeline per record.
|
||||
|
||||
2. **Pipeline-driven**: Deals move through configurable pipeline stages. Each stage has probability weighting, rot thresholds, and optional required-field gating. Stage transitions are first-class events captured in `crm_deal_stage_history` for reliable analytics.
|
||||
|
||||
3. **Perpetual activity log**: The `crm_audit_log` table is immutable and append-only. Every meaningful change is recorded with field-level old/new values. For GDPR, PII is anonymized in-place rather than deleted.
|
||||
|
||||
### Technology Stack
|
||||
|
||||
- **Java 17+** with QQQ backend modules
|
||||
- **QQQ Framework**: Entities, processes, widgets, permissions, API layer
|
||||
- **Database**: RDBMS through QQQ's backend abstraction
|
||||
- **MemoryRecordStore**: Full test suite runs in-memory (no database required)
|
||||
|
||||
### Module Organization
|
||||
|
||||
```
|
||||
qbit-crm/
|
||||
src/main/java/com/kingsrook/qbits/crm/
|
||||
CrmQBitConfig.java -- Configuration (backend, security, companion QBits)
|
||||
CrmQBitProducer.java -- Gen 2 QBitMetaDataProducer (auto-discovers all entities)
|
||||
core/ -- Contacts, Companies, Tags, Attachments, Forms, Email Lists
|
||||
deals/ -- Deals, Pipelines, Stages, Products, Line Items, Stage History
|
||||
activities/ -- Activities, Activity Types, Outcomes, Participants
|
||||
email/ -- Email Messages, Templates, Sequences, Enrollments
|
||||
scoring/ -- Lead Score Rules, Assignment Rules, Sales Goals
|
||||
audit/ -- Immutable audit log + CrmAuditLogCustomizer
|
||||
sync/ -- External system mapping, Currency management
|
||||
widgets/ -- 14 dashboard widgets (producers + renderers)
|
||||
```sh
|
||||
./mvnw -q verify
|
||||
# Windows PowerShell:
|
||||
./mvnw.cmd -q verify
|
||||
# If dependencies are already cached and network resolution is unavailable:
|
||||
./mvnw.cmd -o -q verify
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
Verification runs Checkstyle on production and test sources, architecture guardrails (including strict UI API routes), JUnit tests and JaCoCo 0.8.15. Coverage gates are 70% instructions and 90% classes. The current suite runs **372 tests**, with no failures or skips, including four H2 integration tests. Tests use H2 2.3.232 and AssertJ 3.27.7. CircleCI uses `kingsrook/qqq-orb@0.6.9`.
|
||||
|
||||
### Prerequisites
|
||||
The H2 tests execute the shipped PostgreSQL DDL in PostgreSQL mode. They verify all 33 tables and 395 columns, the 21 declared unique keys, relationship indexes, seven persisted entity types, duplicate list-member rejection and DDL/changelog parity. They do not replace deployment verification against the host's PostgreSQL installation.
|
||||
|
||||
- **Java 17+**
|
||||
- **Maven 3.8+**
|
||||
- **A QQQ Application** with an RDBMS backend (PostgreSQL, MySQL, H2, etc.)
|
||||
- **QQQ Maven Registry** access (for resolving the dependency)
|
||||
## Install in a host application
|
||||
|
||||
### Step 1: Add the Maven dependency
|
||||
1. Build/install this unreleased snapshot with `./mvnw install`, or resolve the same artifact from your configured snapshot repository. The dependency version is explicit; do not assume the QQQ BOM manages this QBit.
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qbits</groupId>
|
||||
<artifactId>qbit-crm</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<groupId>com.kingsrook.qbits</groupId>
|
||||
<artifactId>qbit-crm</artifactId>
|
||||
<version>0.2.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
If you are using the QQQ BOM POM, the version is managed for you:
|
||||
```xml
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-bom-pom</artifactId>
|
||||
<version>${qqq.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
```
|
||||
2. Align the host with QQQ **4.1.0-RC.6**. For a new database, apply [the reference PostgreSQL DDL](docs/ddl/postgres.sql), or include the packaged Liquibase formatted SQL at `db/liquibase/qbit-crm/crm.changelog.sql`. Choose one installation path. The Java producer does not create the schema.
|
||||
|
||||
### Step 2: Configure and produce the QBit
|
||||
|
||||
In your application's metadata setup class (where you build your `QInstance`), add the CRM QBit. The `defaultBackendNameForTables` must match a backend you have already registered with your QInstance (typically your RDBMS backend).
|
||||
3. Register the host backend and authentication, then produce and add the CRM metadata:
|
||||
|
||||
```java
|
||||
import com.kingsrook.qbits.crm.CrmQBitConfig;
|
||||
import com.kingsrook.qbits.crm.CrmQBitProducer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
|
||||
|
||||
// In your QInstance setup method:
|
||||
public void defineQInstance(QInstance qInstance)
|
||||
{
|
||||
// ... your existing backend, auth, and app setup ...
|
||||
|
||||
///////////////////////////////
|
||||
// configure and produce CRM //
|
||||
///////////////////////////////
|
||||
CrmQBitConfig crmConfig = new CrmQBitConfig()
|
||||
.withDefaultBackendNameForTables("rdbms"); // must match a registered backend name
|
||||
|
||||
CrmQBitProducer crmProducer = new CrmQBitProducer()
|
||||
.withQBitConfig(crmConfig);
|
||||
|
||||
MetaDataProducerMultiOutput crmOutput = crmProducer.produce(qInstance);
|
||||
crmOutput.addSelfToInstance(qInstance);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Create the database tables
|
||||
|
||||
The QBit defines 39 tables. On first run against a new database, you need to create the schema. QQQ can auto-create tables if your backend supports it, or you can generate DDL from the entity metadata.
|
||||
|
||||
For **development/testing** with H2 or auto-DDL:
|
||||
```java
|
||||
// QQQ will auto-create tables in memory backends.
|
||||
// For RDBMS with auto-DDL enabled, tables are created on startup.
|
||||
```
|
||||
|
||||
For **production** with PostgreSQL/MySQL, generate migration scripts from the entity field definitions. Each entity class documents its table name (e.g., `crmContact`, `crmCompany`, `crmDeal`) and all field types. Column names in the database are `snake_case` versions of the Java `camelCase` field names (e.g., `firstName` -> `first_name`).
|
||||
|
||||
### Step 4: Add CRM to your app navigation
|
||||
|
||||
```java
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
|
||||
QAppMetaData app = new QAppMetaData()
|
||||
.withName("myApp")
|
||||
.withLabel("My Application")
|
||||
.withSections(List.of(
|
||||
CrmQBitProducer.produceAppSection(),
|
||||
// ... your other app sections ...
|
||||
));
|
||||
qInstance.addApp(app);
|
||||
```
|
||||
|
||||
This adds a "CRM" navigation section to the QQQ Material Dashboard with links to all CRM tables.
|
||||
|
||||
### Step 5 (optional): Seed reference data
|
||||
|
||||
The CRM works without seed data, but you'll want to populate the configuration tables before users start working:
|
||||
|
||||
- **`crmLifecycleStage`**: Lead, Marketing Qualified, Sales Qualified, Opportunity, Customer, Evangelist
|
||||
- **`crmLeadSource`**: Website, Referral, Cold Call, Trade Show, Social Media, Advertisement, Partner
|
||||
- **`crmIndustry`**: Technology, Healthcare, Finance, Manufacturing, Retail, etc.
|
||||
- **`crmActivityType`**: Call, Email, Meeting, Note, Task (with `isSystem=true`)
|
||||
- **`crmActivityOutcome`**: Connected, Left Voicemail, No Answer (for Call type); Completed, No Show, Rescheduled (for Meeting type)
|
||||
- **`crmPipeline`** + **`crmPipelineStage`**: At minimum one pipeline with stages (Prospecting, Qualification, Proposal, Negotiation, Closed Won, Closed Lost)
|
||||
- **`crmContactRole`**: Decision Maker, Influencer, Champion, Budget Holder, End User, Evaluator
|
||||
- **`crmWinLossReason`**: Better Fit (WIN), Price (WIN), Lost to Competitor (LOSS), No Budget (LOSS), etc.
|
||||
- **`crmCurrency`**: At minimum USD with `isBaseCurrency=true` and `exchangeRateToBase=1.0`
|
||||
|
||||
You can insert these via QQQ's `InsertAction` in an application startup process, or directly via SQL.
|
||||
|
||||
### Step 6 (optional): Multi-tenant security
|
||||
|
||||
For SaaS or multi-tenant deployments where each client's CRM data must be isolated:
|
||||
|
||||
```java
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
|
||||
// The host has already registered the backend named "rdbms".
|
||||
CrmQBitConfig crmConfig = new CrmQBitConfig()
|
||||
.withDefaultBackendNameForTables("rdbms")
|
||||
.withRecordSecurityLocks(List.of(
|
||||
new RecordSecurityLock()
|
||||
.withFieldName("clientId")
|
||||
.withSecurityKeyType("crmClientAccess")
|
||||
.withNullValueBehavior(NullValueBehavior.DENY)
|
||||
));
|
||||
.withDefaultBackendNameForTables("rdbms");
|
||||
|
||||
new CrmQBitProducer()
|
||||
.withQBitConfig(crmConfig)
|
||||
.produce(qInstance)
|
||||
.addSelfToInstance(qInstance);
|
||||
|
||||
qInstance.setEnforcePermissionsInCoreActions(true);
|
||||
```
|
||||
|
||||
This injects a `clientId` security field onto all root CRM tables (Contact, Company, Deal, Pipeline) and propagates security locks through join chains to child tables. Users only see records matching their security key values.
|
||||
4. Map the host's authenticated roles to the permissions below. Configure the host's core audit storage. Add tables, processes and widgets to the host UI as needed: `produceAppSection()` currently returns an empty CRM section; P1 does not provide an assembled app or home dashboard.
|
||||
|
||||
### Step 7 (optional): Companion QBits
|
||||
## Configuration and access
|
||||
|
||||
The CRM integrates with other QBits for enhanced functionality:
|
||||
- `defaultBackendNameForTables`: required backend name.
|
||||
- `inferBackendNames`: defaults to true; sets snake_case SQL column names while Java/QQQ field names remain camelCase. SQL table names equal the QQQ table names. Set false only for an explicit host column mapping.
|
||||
- `tableMetaDataCustomizer`: optional host table customizer.
|
||||
- `securityFields` and `recordSecurityLocks`: retained in P1, applied to contact, company and deal. Host-managed schema columns must match these fields. This is not yet tenant isolation across every child table or workflow.
|
||||
|
||||
```java
|
||||
CrmQBitConfig crmConfig = new CrmQBitConfig()
|
||||
.withDefaultBackendNameForTables("rdbms")
|
||||
.withQuickSearchQBitNamespace("quickSearch") // full-text search across contacts/companies/deals
|
||||
.withWebhooksQBitNamespace("webhooks") // fire events on CRM changes (deal won, contact created, etc.)
|
||||
.withWorkflowsQBitNamespace("workflows"); // user-defined automation rules
|
||||
```
|
||||
| Scope | Permissions |
|
||||
|---|---|
|
||||
| Main and sub-tables | `crm.read`, `crm.write` |
|
||||
| Settings tables except currency | `crm.manage.read`, `crm.manage.write` |
|
||||
| Currency | `crm.admin.read`, `crm.admin.write` |
|
||||
| Widgets and everyday processes | `crm.hasAccess` |
|
||||
| Merge, bulk assignment, list and sequence management processes | `crm.manage.hasAccess` |
|
||||
| GDPR anonymization | `crm.admin.hasAccess` |
|
||||
|
||||
Each companion QBit must be separately added as a Maven dependency and produced into your QInstance. The namespace strings must match the namespaces you used when producing those QBits.
|
||||
Permissions are independent grants. Every CRM-produced table, process and widget has explicit `QPermissionRules` with `DenyBehavior.HIDDEN` and QBit provenance. `security.CrmPermissions.ALL` lists the grants for host administration and tests. The widget provenance adapter supplies core's `SourceQBitAware` interface because RC.6's widget class lacks it.
|
||||
|
||||
### Complete example
|
||||
## Tables
|
||||
|
||||
```java
|
||||
public void defineQInstance(QInstance qInstance)
|
||||
{
|
||||
///////////////
|
||||
// backend //
|
||||
///////////////
|
||||
qInstance.addBackend(new RDBMSBackendMetaData()
|
||||
.withName("rdbms")
|
||||
.withVendor("postgresql")
|
||||
.withHostName("localhost")
|
||||
.withDatabaseName("myapp")
|
||||
.withUsername("myapp")
|
||||
.withPassword("secret"));
|
||||
All 33 table names and table-backed possible-value sources use `crm_` snake_case. The 23 enum possible-value sources retain their enum names. Relationship columns have indexes; the reference DDL does not impose foreign-key constraints or host-specific tenant columns. Unique constraints match the current QQQ metadata.
|
||||
|
||||
////////////////////
|
||||
// authentication //
|
||||
////////////////////
|
||||
qInstance.setAuthentication(new Auth0AuthenticationMetaData()
|
||||
.withType(QAuthenticationType.AUTH_0)
|
||||
// ... auth config ...
|
||||
);
|
||||
### Main tables (6)
|
||||
|
||||
//////////
|
||||
// CRM //
|
||||
//////////
|
||||
CrmQBitConfig crmConfig = new CrmQBitConfig()
|
||||
.withDefaultBackendNameForTables("rdbms");
|
||||
`crm_contact`, `crm_company`, `crm_deal`, `crm_activity`, `crm_form_submission`, `crm_email_list`.
|
||||
|
||||
new CrmQBitProducer()
|
||||
.withQBitConfig(crmConfig)
|
||||
.produce(qInstance)
|
||||
.addSelfToInstance(qInstance);
|
||||
### Settings tables (15)
|
||||
|
||||
//////////
|
||||
// App //
|
||||
//////////
|
||||
qInstance.addApp(new QAppMetaData()
|
||||
.withName("myApp")
|
||||
.withSections(List.of(
|
||||
CrmQBitProducer.produceAppSection()
|
||||
)));
|
||||
}
|
||||
```
|
||||
`crm_pipeline`, `crm_product`, `crm_email_template`, `crm_email_sequence`, `crm_lead_score_rule`, `crm_assignment_rule`, `crm_sales_goal`, `crm_currency`, `crm_lifecycle_stage`, `crm_lead_source`, `crm_industry`, `crm_activity_type`, `crm_activity_outcome`, `crm_contact_role`, `crm_win_loss_reason`.
|
||||
|
||||
## Data Model
|
||||
### Sub-tables (12)
|
||||
|
||||
### Tables (39 total)
|
||||
`crm_pipeline_stage`, `crm_consent_record`, `crm_email_list_member`, `crm_email_message`, `crm_sequence_step`, `crm_sequence_enrollment`, `crm_deal_contact`, `crm_deal_product`, `crm_deal_stage_history`, `crm_activity_participant`, `crm_assignment_rule_member`, `crm_external_mapping`.
|
||||
|
||||
#### Core (Phase 1a)
|
||||
Contact, company, deal and activity declare core FIELD-level audit rules. The bespoke tag tables, attachment table, audit-log table and bulk tag processes have been removed. 0.2.0 is a fresh-install release with no upgrade path from 0.1.0. Collaboration replacements arrive in a later phase.
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `crmContact` | People with lifecycle stages, lead scoring, consent tracking |
|
||||
| `crmCompany` | Organizations with industry, type, parent-child hierarchy |
|
||||
| `crmLifecycleStage` | Configurable contact lifecycle (Lead, MQL, SQL, Customer, etc.) |
|
||||
| `crmLeadSource` | How contacts were acquired (Website, Referral, etc.) |
|
||||
| `crmIndustry` | Industry classification for companies |
|
||||
| `crmTag` | Free-form tags for categorizing records |
|
||||
| `crmContactTag` / `crmCompanyTag` | Tag junction tables |
|
||||
| `crmAttachment` | File attachments on any entity (polymorphic) |
|
||||
| `crmConsentRecord` | GDPR consent tracking (append-only) |
|
||||
| `crmFormSubmission` | Website form capture with UTM parameters |
|
||||
| `crmEmailList` | Marketing mailing lists |
|
||||
| `crmEmailListMember` | List subscribers (standalone or linked to contacts) |
|
||||
| `crmCurrency` | Exchange rates for multi-currency support |
|
||||
| `crmAuditLog` | Immutable field-level change tracking |
|
||||
## Processes
|
||||
|
||||
#### Activities
|
||||
The QBit produces 25 processes with these names and labels. QQQ may additionally generate standard table actions such as `<table>.bulkInsert`; those retain core's naming conventions. CRM processes are available to host wiring and are not registered as scheduled jobs.
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `crmActivity` | Unified table for calls, emails, meetings, notes, tasks |
|
||||
| `crmActivityType` | Configurable activity types with icons |
|
||||
| `crmActivityOutcome` | Call/meeting outcomes (Connected, No Answer, etc.) |
|
||||
| `crmActivityParticipant` | Additional participants on activities |
|
||||
| Process name | Label |
|
||||
|---|---|
|
||||
| `crm_auto_assign` | Auto Assign |
|
||||
| `crm_bulk_assign_owner` | Bulk Assign Owner |
|
||||
| `crm_calculate_lead_scores` | Calculate Lead Scores |
|
||||
| `crm_clone_deal` | Clone Deal |
|
||||
| `crm_convert_form_to_contact` | Convert Form to Contact |
|
||||
| `crm_convert_lead` | Convert Lead |
|
||||
| `crm_detect_duplicates` | Detect Duplicates |
|
||||
| `crm_enroll_in_sequence` | Enroll in Sequence |
|
||||
| `crm_fire_reminders` | Fire Reminders |
|
||||
| `crm_gdpr_anonymize_contact` | GDPR Anonymize Contact |
|
||||
| `crm_import_contacts` | Import Contacts |
|
||||
| `crm_log_activity` | Log Activity |
|
||||
| `crm_manage_contact_on_deal` | Manage Contact on Deal |
|
||||
| `crm_merge_companies` | Merge Companies |
|
||||
| `crm_merge_contacts` | Merge Contacts |
|
||||
| `crm_move_deal_stage` | Move Deal Stage |
|
||||
| `crm_process_overdue_tasks` | Process Overdue Tasks |
|
||||
| `crm_process_sequence_steps` | Process Sequence Steps |
|
||||
| `crm_reopen_deal` | Reopen Deal |
|
||||
| `crm_send_email` | Send Email |
|
||||
| `crm_subscribe_to_list` | Subscribe to List |
|
||||
| `crm_track_email_event` | Track Email Event |
|
||||
| `crm_transfer_ownership` | Transfer Ownership |
|
||||
| `crm_unenroll_from_sequence` | Unenroll from Sequence |
|
||||
| `crm_unsubscribe_from_list` | Unsubscribe from List |
|
||||
|
||||
#### Deals & Pipeline (Phase 1b)
|
||||
## Current behavior and limits
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `crmPipeline` | Pipeline definitions (multiple pipelines supported) |
|
||||
| `crmPipelineStage` | Stages with probability, rot days, required-field gating |
|
||||
| `crmDeal` | Deals with multi-currency, weighted amounts, probability override |
|
||||
| `crmDealContact` | Contact-to-deal junction with roles and primary flag |
|
||||
| `crmContactRole` | Roles contacts play on deals (Decision Maker, etc.) |
|
||||
| `crmWinLossReason` | Win/loss reasons for closed deals |
|
||||
| `crmDealTag` | Deal tag junction |
|
||||
| `crmDealStageHistory` | Stage transition log for pipeline analytics |
|
||||
The existing pipeline/stage logic, stage history, product totals, currency conversion, contact/company merge, activity logging, form auto-linking, list subscribe/unsubscribe, scoring and assignment processes remain. Fourteen custom dashboard widget producers remain available for host placement, together with generated child-record widgets.
|
||||
|
||||
#### Email (Phase 2)
|
||||
Some process names describe unfinished behavior inherited from 0.1.0:
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `crmEmailTemplate` | Reusable templates with merge field support |
|
||||
| `crmEmailMessage` | Tracked emails with open/click/bounce/spam detection |
|
||||
- Send Email and sequence email steps record CRM activity/message state; they do not deliver email. Template merging and live delivery tracking are not implemented here.
|
||||
- Sequence execution, lead scoring and overdue-task work require host invocation. No schedules are shipped. Assignment is a process, not an automatic insert hook; reminders do not deliver notifications.
|
||||
- Scoring's date-decay operators remain unimplemented. Import Contacts expects pre-parsed input and does not provide the planned streamed bulk-load workflow.
|
||||
- GDPR anonymization scrubs the contact, unenrolls sequences and records withdrawn consent. It does not yet erase all linked PII or core audit details. The P1 schema retains existing PII fields; classification, complete erasure and redesigned tenancy are later work.
|
||||
|
||||
#### Products (Phase 2)
|
||||
The later intake APIs, public subscription routes, collaboration integration, real email transport, durable jobs and full app navigation are not part of this phase.
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `crmProduct` | Product catalog with SKU and pricing |
|
||||
| `crmDealProduct` | Line items on deals with quantity, discount, calculated totals |
|
||||
## Source layout
|
||||
|
||||
#### Sequences & Scoring (Phase 3)
|
||||
`CrmQBitProducer` discovers entity, process and widget producers. `core/` holds contacts, companies, forms, lists and consent; `deals/`, `activities/`, `email/` and `scoring/` hold their respective metadata and behavior. `security/` defines permissions, `sync/` holds external mappings and currency utilities, and `widgets/` contains widget producers/renderers. SQL is shipped under `src/main/resources/db/liquibase/qbit-crm/`.
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `crmEmailSequence` | Multi-step outreach sequences with send windows |
|
||||
| `crmSequenceStep` | Individual steps (email, call, task, wait) |
|
||||
| `crmSequenceEnrollment` | Contact enrollment tracking with failure handling |
|
||||
| `crmLeadScoreRule` | Scoring rules with dot-notation field paths |
|
||||
| `crmAssignmentRule` | Auto-assignment rules (round-robin, least-active, specific user) |
|
||||
| `crmAssignmentRuleMember` | Users in assignment pools |
|
||||
| `crmSalesGoal` | Revenue quotas per user per period |
|
||||
|
||||
#### Sync (Phase 2)
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `crmExternalMapping` | Bidirectional sync mapping for external systems |
|
||||
|
||||
### Enum PossibleValueSources (24)
|
||||
|
||||
CrmEntityType, CrmCompanyType, CrmDealPriority, CrmActivityPriority, CrmDirection, CrmBounceType, CrmWinLossType, CrmParticipantRole, CrmSequenceStepType, CrmEnrollmentStatus, CrmEmailStatus, CrmScorableEntity, CrmScoreOperator, CrmAssignableEntity, CrmAssignStrategy, CrmConsentType, CrmConsentStatus, CrmLegalBasis, CrmGoalPeriod, CrmSyncStatus, CrmAuditAction, CrmFormType, CrmFormConversionStatus, CrmListMemberStatus
|
||||
|
||||
## Processes (27)
|
||||
|
||||
### Core Operations
|
||||
|
||||
| Process | Description |
|
||||
|---------|-------------|
|
||||
| `logActivity` | Quick-log a call, meeting, note, or task from any record |
|
||||
| `convertFormToContact` | Convert website form submission to CRM contact |
|
||||
| `bulkConvertForms` | Batch convert multiple form submissions |
|
||||
| `subscribeToList` / `unsubscribeFromList` | Mailing list management |
|
||||
| `bulkAssignOwner` | Reassign record ownership in bulk |
|
||||
| `bulkAddTag` / `bulkRemoveTag` | Tag management in bulk |
|
||||
| `transferOwnership` | Transfer all records from one user to another |
|
||||
|
||||
### Deal Operations
|
||||
|
||||
| Process | Description |
|
||||
|---------|-------------|
|
||||
| `moveDealStage` | Move deal to new stage with required-field validation |
|
||||
| `reopenDeal` | Reopen a closed deal to an active stage |
|
||||
| `convertLead` | Convert contact to deal (atomic: lifecycle + deal + junction) |
|
||||
| `manageContactOnDeal` | Add/remove contacts with role and primary flag enforcement |
|
||||
| `cloneDeal` | Duplicate deal with contacts, products, and tags |
|
||||
|
||||
### Email & Sequences
|
||||
|
||||
| Process | Description |
|
||||
|---------|-------------|
|
||||
| `sendEmail` | Compose and send tracked email with template resolution |
|
||||
| `trackEmailEvent` | Webhook callback for open/click/bounce/spam events |
|
||||
| `enrollInSequence` | Enroll contacts in multi-step sequences |
|
||||
| `processSequenceSteps` | Scheduled: execute pending sequence steps (5 min interval) |
|
||||
| `unenrollFromSequence` | Remove contact from active sequence |
|
||||
|
||||
### Scoring & Assignment
|
||||
|
||||
| Process | Description |
|
||||
|---------|-------------|
|
||||
| `calculateLeadScores` | Scheduled daily: recalculate scores from rules |
|
||||
| `autoAssign` | POST_INSERT hook: first-match rule-based assignment |
|
||||
|
||||
### Data Management
|
||||
|
||||
| Process | Description |
|
||||
|---------|-------------|
|
||||
| `mergeContacts` / `mergeCompanies` | Merge duplicates with unique-key conflict resolution |
|
||||
| `importContacts` | CSV import with validation and duplicate detection |
|
||||
| `detectDuplicates` | Find duplicate contacts by email or companies by domain |
|
||||
| `gdprAnonymizeContact` | Right-to-erasure: anonymize PII, scrub audit log, delete attachments |
|
||||
|
||||
### Scheduled Maintenance
|
||||
|
||||
| Process | Description |
|
||||
|---------|-------------|
|
||||
| `processOverdueTasks` | Hourly: detect overdue task activities |
|
||||
| `fireReminders` | Every 5 min: fire and clear activity reminders |
|
||||
|
||||
## Widgets (14)
|
||||
|
||||
| Widget | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `crmPipelineSummary` | Table | Deal count and amount per stage |
|
||||
| `crmActivityFeed` | Table | Most recent activities across all entities |
|
||||
| `crmDealForecast` | Table | Weighted pipeline by expected close month |
|
||||
| `crmRottingDeals` | Table | Deals exceeding stage rot threshold |
|
||||
| `crmContactTimeline` | Stepper | Per-contact activity timeline |
|
||||
| `crmActivityCount` | Statistics | Activity counts by type (today/week/month) |
|
||||
| `crmRecentDeals` | Table | Recently won/lost deals |
|
||||
| `crmSalesLeaderboard` | Table | Users ranked by closed-won revenue |
|
||||
| `crmQuotaAttainment` | Statistics | Won revenue vs. sales goal target |
|
||||
| `crmPipelineConversion` | Table | Stage-to-stage conversion rates |
|
||||
| `crmStageDuration` | Table | Average days per pipeline stage |
|
||||
| `crmWinRate` | Table | Win rate trend over time |
|
||||
| `crmAvgDealSize` | Table | Average won deal size |
|
||||
| `crmSalesCycleLength` | Table | Average days from creation to close |
|
||||
|
||||
## Companion QBit Integration
|
||||
|
||||
### qbit-quick-search (ElasticSearch)
|
||||
|
||||
Index `crmContact`, `crmCompany`, and `crmDeal` for full-text global search.
|
||||
|
||||
### qbit-webhooks
|
||||
|
||||
Supported event types:
|
||||
|
||||
| Event | Fires When |
|
||||
|-------|------------|
|
||||
| `crm.contact.created` / `crm.contact.updated` | Contact inserted or updated |
|
||||
| `crm.company.created` / `crm.company.updated` | Company inserted or updated |
|
||||
| `crm.deal.created` / `crm.deal.stageChanged` | Deal created or stage changed |
|
||||
| `crm.deal.won` / `crm.deal.lost` | Deal moved to terminal stage |
|
||||
| `crm.activity.created` | Activity logged |
|
||||
| `crm.consent.changed` | Consent granted or withdrawn |
|
||||
| `crm.form.submitted` / `crm.form.converted` | Form submitted or converted to contact |
|
||||
|
||||
### qbit-workflows
|
||||
|
||||
User-defined automation beyond built-in scoring/assignment. The CRM does not re-implement a workflow engine.
|
||||
|
||||
## Phased Delivery
|
||||
|
||||
| Phase | Tables | Processes | Widgets | Focus |
|
||||
|-------|--------|-----------|---------|-------|
|
||||
| 1a | 18 | 7 | 6 | Core + Website Integration |
|
||||
| 1b | 8 | 4 | 3 | Deals + Pipeline |
|
||||
| 2 | 5 | 7 | 1 | Email, Products, Scheduling |
|
||||
| 3 | 7 | 5 | 2 | Sequences, Scoring, Automation |
|
||||
| 4 | 1 | 7 | 5 | Data Management, Analytics, GDPR |
|
||||
| **Total** | **39** | **30** | **14** | |
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
mvn test # Run all 271 tests
|
||||
mvn test -Dtest=BaseTest # Verify QBit produces metadata
|
||||
```
|
||||
|
||||
### Coverage
|
||||
|
||||
- **91.5% instruction coverage** (Jacoco)
|
||||
- **73 test classes** covering all entities, processes, widgets, and customizers
|
||||
- All tests run in-memory via MemoryRecordStore (no database required)
|
||||
|
||||
```bash
|
||||
mvn org.jacoco:jacoco-maven-plugin:0.8.11:prepare-agent test \
|
||||
org.jacoco:jacoco-maven-plugin:0.8.11:report
|
||||
# Report at: target/site/jacoco/index.html
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[QQQ Wiki](https://github.com/Kingsrook/qqq/wiki)** - Framework documentation
|
||||
- **[QBit Development Guide](https://github.com/Kingsrook/qqq/wiki/QBit-Development)** - How QBits work
|
||||
- **Design Spec** - Full data model and architecture in `QRun-IO/specs/2026-03-28-qbit-crm-design-v2.md`
|
||||
|
||||
## Contributing
|
||||
|
||||
QBit CRM is open source and welcomes contributions.
|
||||
|
||||
- **[Report Issues](https://github.com/QRun-IO/qqq/issues)** - Bug reports and feature requests
|
||||
- **[QQQ Contribution Guide](https://github.com/Kingsrook/qqq/wiki/Contribution-Guidelines)** - How to contribute
|
||||
|
||||
## About Kingsrook
|
||||
|
||||
QBit CRM is built by **[Kingsrook](https://qrun.io)** - making engineers more productive through intelligent automation and developer tools.
|
||||
|
||||
- **Website**: [https://qrun.io](https://qrun.io)
|
||||
- **Contact**: [contact@kingsrook.com](mailto:contact@kingsrook.com)
|
||||
- **GitHub**: [https://github.com/QRun-IO](https://github.com/QRun-IO)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the **Apache License, Version 2.0** - see the [LICENSE](LICENSE) file for details.
|
||||
Licensed under [Apache License 2.0](LICENSE).
|
||||
+9
-10
@@ -6,18 +6,17 @@
|
||||
~ contact@kingsrook.com
|
||||
~ https://github.com/Kingsrook/
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU Affero General Public License as
|
||||
~ published by the Free Software Foundation, either version 3 of the
|
||||
~ License, or (at your option) any later version.
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU Affero General Public License for more details.
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ You should have received a copy of the GNU Affero General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<!DOCTYPE module PUBLIC
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
@@ -9,7 +9,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
||||
+17
-20
@@ -7,8 +7,9 @@ the `/v2/contact` form and newsletter capture). The document records three thing
|
||||
- the target design
|
||||
- the integration contracts and the delivery phases
|
||||
|
||||
0.2.0 is a breaking release: tables are renamed, and several bespoke features are replaced by
|
||||
platform features. The [upgrade notes](#upgrading-from-010) cover existing installs (Voyage).
|
||||
0.2.0 is a breaking, fresh-install release: tables are renamed, and several bespoke features are
|
||||
replaced by platform features. 0.1.0 never went live anywhere (Voyage was a proof of concept), so
|
||||
there is no upgrade path or compatibility layer.
|
||||
|
||||
## Goals
|
||||
|
||||
@@ -79,7 +80,7 @@ These parts are real and are kept, adapted to 0.2.0:
|
||||
|---|---|---|
|
||||
| Build | parent 1.4.0, QQQ 0.27.9, Java 17, orb 0.3.8 | parent 2.1.2 (guardrails profile), QQQ 4.1.0-RC.6 (RC.7 when cut), Java 21, `./mvnw`, orb 0.6.9 |
|
||||
| Table names | `crmContact` (camelCase) | `crm_contact`, backend name = table name, snake_case columns |
|
||||
| Permissions | none | `crm.view`, `crm.edit`, `crm.manage`, `crm.admin` (see [Security](#security-tenancy-and-pii)) |
|
||||
| Permissions | none | `crm`, `crm.manage`, `crm.admin` bases (see [Security](#security-tenancy-and-pii)) |
|
||||
| UI | n/a | `/qqq/v1` only; the one public machine route is under `/qqq/hooks/crm/` (G2, G3) |
|
||||
| Messaging | own (fake) sending | qbit-email public API (allow-listed by G1) or core `SendMessageAction` |
|
||||
| Collaboration | bespoke tags, attachments, reminders | core `RecordFeaturesMetaData` (TAGS, COMMENTS, ATTACHMENTS, FOLLOW, TASKS, ASSIGNMENT, TIMELINE) |
|
||||
@@ -315,10 +316,9 @@ Server-side validation (`ValidateContingentPossibleValuesHelper`) is on for stag
|
||||
|
||||
| Permission | Allows |
|
||||
|---|---|
|
||||
| `crm.view` | Read all CRM tables |
|
||||
| `crm.edit` | Insert, edit and delete on main and sub-tables; everyday actions |
|
||||
| `crm.manage` | Settings tables; merge, bulk assign, list and sequence management |
|
||||
| `crm.admin` | Erase personal data, consent overrides, currency rates |
|
||||
| `crm.hasAccess`, `crm.read`, `crm.write` | The app; main and sub-tables; everyday actions |
|
||||
| `crm.manage.*` | Settings tables (including currencies); merge, bulk assign, list and sequence management |
|
||||
| `crm.admin.*` | Erase personal data and consent overrides |
|
||||
|
||||
Each table's standard QQQ suffixes map onto these, through `QPermissionRules` with
|
||||
`DenyBehavior.HIDDEN`.
|
||||
@@ -332,7 +332,7 @@ default) does three things:
|
||||
The host maps Authentik groups or claims to keys in its `AuthentikPolicyClaimMapper` override.
|
||||
An all-access key is configurable. Widgets, processes and the API query with the session, so
|
||||
every read is scoped. kof22 is single-tenant and leaves this off. The 0.1.0 `securityFields` and
|
||||
`recordSecurityLocks` config is replaced, and the upgrade notes cover it.
|
||||
`recordSecurityLocks` config is replaced.
|
||||
|
||||
**PII classification** (`DataClassification.PII`):
|
||||
- contact: names, emails, phones, address, LinkedIn, time zone
|
||||
@@ -435,18 +435,15 @@ The adoption changes:
|
||||
- **Marketing:** qbit-marketing on kof22 uses `QbitCrmGateway`, which delegates to
|
||||
`CrmMarketingApi`.
|
||||
|
||||
## Upgrading from 0.1.0
|
||||
## No upgrade path from 0.1.0
|
||||
|
||||
- **Table renames:** 0.2.0 renames every table and column. `docs/MIGRATION-0.2.md` lists the
|
||||
Postgres `ALTER TABLE ... RENAME` statements.
|
||||
- **Removed tables:** the tag, attachment and audit tables are dropped after export. Tags can be
|
||||
re-applied through collaboration.
|
||||
- **Voyage:** Voyage pins 0.1.0 and has no CRM DDL. It moves when its owner chooses, using the
|
||||
migration notes.
|
||||
- **Config:** the 0.1.0 config options `tableNamePrefix`, `quickSearchQBitNamespace`,
|
||||
`webhooksQBitNamespace`, `workflowsQBitNamespace`, `securityFields` and `recordSecurityLocks`
|
||||
are removed. Use `withTenantSecurityKeyType`, `withApiName`, `withEsbDestinationName` and
|
||||
`withSchedulesEnabled`.
|
||||
0.1.0 never went live. 0.2.0 is a fresh install: there are no rename scripts and no compatibility shims. These 0.1.0 config options are removed:
|
||||
- `tableNamePrefix`
|
||||
- `quickSearchQBitNamespace`
|
||||
- `webhooksQBitNamespace`
|
||||
- `workflowsQBitNamespace`
|
||||
|
||||
`securityFields` and `recordSecurityLocks` are replaced by `withTenantSecurityKeyType` in P4.
|
||||
|
||||
## Delivery phases
|
||||
|
||||
@@ -466,7 +463,7 @@ accepts every recommendation above. Specifically:
|
||||
|
||||
| # | Decision |
|
||||
|---|---|
|
||||
| 1 | 0.2.0 is a breaking release with `crm_` names. Voyage stays on 0.1.0 until its owner migrates. |
|
||||
| 1 | 0.2.0 is a breaking, fresh-install release with `crm_` names and no compatibility work for 0.1.0. Voyage was a proof of concept (owner, 2026-10-14). |
|
||||
| 2 | Tags, attachments, reminders and audit move to collaboration, alerts and core audit. The bespoke tables are removed. |
|
||||
| 3 | The CRM never sends email itself: it uses qbit-email first, then core messaging. |
|
||||
| 4 | Lists use double opt-in by default. Contact forms never grant marketing consent without an explicit opt-in. |
|
||||
|
||||
@@ -0,0 +1,606 @@
|
||||
-- Copyright 2026 Kingsrook, LLC. Licensed under the Apache License, Version 2.0.
|
||||
-- https://www.apache.org/licenses/LICENSE-2.0
|
||||
-- QBit CRM 0.2.0 P1 PostgreSQL reference schema (33 tables).
|
||||
-- QQQ table names equal SQL names; Java field names map to snake_case columns.
|
||||
-- Unique constraints match QQQ metadata. Relationship columns are indexed;
|
||||
-- foreign-key constraints are deliberately not imposed (the QQQ/host layer owns
|
||||
-- validation and deletion policy, including polymorphic and optional references).
|
||||
-- Optional host-configured securityFields require matching host-managed columns.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_activity (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
activity_type_id INTEGER NOT NULL,
|
||||
subject VARCHAR(500) NOT NULL,
|
||||
description TEXT,
|
||||
contact_id INTEGER,
|
||||
company_id INTEGER,
|
||||
deal_id INTEGER,
|
||||
owner_user_id TEXT NOT NULL,
|
||||
assigned_to_user_id TEXT,
|
||||
start_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
end_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
due_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
completed_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
is_completed BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
priority INTEGER,
|
||||
duration_minutes INTEGER,
|
||||
outcome_id INTEGER,
|
||||
location VARCHAR(500),
|
||||
conference_link VARCHAR(1000),
|
||||
direction INTEGER,
|
||||
is_reminder_set BOOLEAN DEFAULT FALSE,
|
||||
reminder_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
external_calendar_id VARCHAR(500),
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_activity_type_id ON crm_activity (activity_type_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_contact_id ON crm_activity (contact_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_company_id ON crm_activity (company_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_deal_id ON crm_activity (deal_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_owner_user_id ON crm_activity (owner_user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_assigned_to_user_id ON crm_activity (assigned_to_user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_outcome_id ON crm_activity (outcome_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_external_calendar_id ON crm_activity (external_calendar_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_activity_outcome (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
activity_type_id INTEGER NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_activity_outcome_1 UNIQUE (activity_type_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_outcome_activity_type_id ON crm_activity_outcome (activity_type_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_activity_participant (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
activity_id INTEGER NOT NULL,
|
||||
contact_id INTEGER,
|
||||
user_id TEXT,
|
||||
role INTEGER,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_participant_activity_id ON crm_activity_participant (activity_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_participant_contact_id ON crm_activity_participant (contact_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_activity_participant_user_id ON crm_activity_participant (user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_activity_type (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
icon_name VARCHAR(50),
|
||||
is_system BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_activity_type_1 UNIQUE (name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_assignment_rule (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(250) NOT NULL,
|
||||
description TEXT,
|
||||
entity_type INTEGER NOT NULL,
|
||||
criteria_json TEXT,
|
||||
assign_strategy INTEGER NOT NULL,
|
||||
assign_to_user_id TEXT,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_assignment_rule_assign_to_user_id ON crm_assignment_rule (assign_to_user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_assignment_rule_member (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
assignment_rule_id INTEGER NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
last_assigned_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_assignment_rule_member_1 UNIQUE (assignment_rule_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_assignment_rule_member_assignment_rule_id ON crm_assignment_rule_member (assignment_rule_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_assignment_rule_member_user_id ON crm_assignment_rule_member (user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_company (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(250) NOT NULL,
|
||||
domain VARCHAR(250),
|
||||
company_type INTEGER,
|
||||
industry_id INTEGER,
|
||||
employee_count INTEGER,
|
||||
annual_revenue DECIMAL(20,6),
|
||||
annual_revenue_currency_code VARCHAR(3) DEFAULT 'USD',
|
||||
phone VARCHAR(50),
|
||||
website VARCHAR(500),
|
||||
address_line1 VARCHAR(250),
|
||||
address_line2 VARCHAR(250),
|
||||
city VARCHAR(100),
|
||||
state VARCHAR(100),
|
||||
postal_code VARCHAR(20),
|
||||
country VARCHAR(100),
|
||||
owner_user_id TEXT NOT NULL,
|
||||
parent_company_id INTEGER,
|
||||
description TEXT,
|
||||
last_activity_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
last_contacted_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_company_industry_id ON crm_company (industry_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_company_owner_user_id ON crm_company (owner_user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_company_parent_company_id ON crm_company (parent_company_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_consent_record (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
contact_id INTEGER NOT NULL,
|
||||
consent_type INTEGER NOT NULL,
|
||||
status INTEGER NOT NULL,
|
||||
legal_basis INTEGER,
|
||||
source VARCHAR(250),
|
||||
consent_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
expiry_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
ip_address VARCHAR(45),
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_consent_record_contact_id ON crm_consent_record (contact_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_contact (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
first_name VARCHAR(100) NOT NULL,
|
||||
last_name VARCHAR(100) NOT NULL,
|
||||
email VARCHAR(250),
|
||||
secondary_email VARCHAR(250),
|
||||
phone VARCHAR(50),
|
||||
mobile_phone VARCHAR(50),
|
||||
job_title VARCHAR(150),
|
||||
department VARCHAR(100),
|
||||
company_id INTEGER,
|
||||
lifecycle_stage_id INTEGER,
|
||||
lead_source_id INTEGER,
|
||||
owner_user_id TEXT NOT NULL,
|
||||
address_line1 VARCHAR(250),
|
||||
address_line2 VARCHAR(250),
|
||||
city VARCHAR(100),
|
||||
state VARCHAR(100),
|
||||
postal_code VARCHAR(20),
|
||||
country VARCHAR(100),
|
||||
timezone VARCHAR(50),
|
||||
preferred_language VARCHAR(10),
|
||||
website VARCHAR(500),
|
||||
linkedin_url VARCHAR(500),
|
||||
description TEXT,
|
||||
lead_score INTEGER DEFAULT 0,
|
||||
do_not_email BOOLEAN DEFAULT FALSE,
|
||||
do_not_call BOOLEAN DEFAULT FALSE,
|
||||
unsubscribe_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
last_activity_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
last_contacted_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_contact_company_id ON crm_contact (company_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_contact_lifecycle_stage_id ON crm_contact (lifecycle_stage_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_contact_lead_source_id ON crm_contact (lead_source_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_contact_owner_user_id ON crm_contact (owner_user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_contact_role (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_contact_role_1 UNIQUE (name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_currency (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
currency_code VARCHAR(3) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
symbol VARCHAR(5),
|
||||
exchange_rate_to_base DECIMAL(20,6) DEFAULT 1.0 NOT NULL,
|
||||
is_base_currency BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
rate_last_updated_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_currency_1 UNIQUE (currency_code)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_deal (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(250) NOT NULL,
|
||||
pipeline_id INTEGER NOT NULL,
|
||||
pipeline_stage_id INTEGER NOT NULL,
|
||||
amount DECIMAL(20,6),
|
||||
currency_code VARCHAR(3) DEFAULT 'USD',
|
||||
amount_in_base_currency DECIMAL(20,6),
|
||||
probability_override_pct INTEGER,
|
||||
weighted_amount DECIMAL(20,6),
|
||||
company_id INTEGER,
|
||||
owner_user_id TEXT NOT NULL,
|
||||
lead_source_id INTEGER,
|
||||
expected_close_date DATE,
|
||||
actual_close_date DATE,
|
||||
win_loss_reason_id INTEGER,
|
||||
stage_entered_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
description TEXT,
|
||||
priority INTEGER,
|
||||
last_activity_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_pipeline_id ON crm_deal (pipeline_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_pipeline_stage_id ON crm_deal (pipeline_stage_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_company_id ON crm_deal (company_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_owner_user_id ON crm_deal (owner_user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_lead_source_id ON crm_deal (lead_source_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_win_loss_reason_id ON crm_deal (win_loss_reason_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_deal_contact (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
deal_id INTEGER NOT NULL,
|
||||
contact_id INTEGER NOT NULL,
|
||||
contact_role_id INTEGER,
|
||||
is_primary BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_deal_contact_1 UNIQUE (deal_id, contact_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_contact_deal_id ON crm_deal_contact (deal_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_contact_contact_id ON crm_deal_contact (contact_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_contact_contact_role_id ON crm_deal_contact (contact_role_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_deal_product (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
deal_id INTEGER NOT NULL,
|
||||
product_id INTEGER NOT NULL,
|
||||
quantity DECIMAL(20,6) DEFAULT 1 NOT NULL,
|
||||
unit_price DECIMAL(20,6) NOT NULL,
|
||||
discount_pct DECIMAL(20,6) DEFAULT 0,
|
||||
total_amount DECIMAL(20,6),
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_product_deal_id ON crm_deal_product (deal_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_product_product_id ON crm_deal_product (product_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_deal_stage_history (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
deal_id INTEGER NOT NULL,
|
||||
from_stage_id INTEGER,
|
||||
to_stage_id INTEGER NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
transition_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
duration_in_from_stage_days INTEGER,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_stage_history_deal_id ON crm_deal_stage_history (deal_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_stage_history_from_stage_id ON crm_deal_stage_history (from_stage_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_stage_history_to_stage_id ON crm_deal_stage_history (to_stage_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_deal_stage_history_user_id ON crm_deal_stage_history (user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_email_list (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(250) NOT NULL,
|
||||
slug VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
is_public BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
member_count INTEGER DEFAULT 0,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_email_list_1 UNIQUE (name),
|
||||
CONSTRAINT uq_crm_email_list_2 UNIQUE (slug)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_email_list_member (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
email_list_id INTEGER NOT NULL,
|
||||
contact_id INTEGER,
|
||||
email VARCHAR(250) NOT NULL,
|
||||
first_name VARCHAR(100),
|
||||
status INTEGER DEFAULT 1 NOT NULL,
|
||||
subscribed_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
unsubscribed_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
subscription_source VARCHAR(250),
|
||||
form_submission_id INTEGER,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_email_list_member_1 UNIQUE (email_list_id, email)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_email_list_member_email_list_id ON crm_email_list_member (email_list_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_email_list_member_contact_id ON crm_email_list_member (contact_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_email_list_member_form_submission_id ON crm_email_list_member (form_submission_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_email_message (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
activity_id INTEGER,
|
||||
from_address VARCHAR(250) NOT NULL,
|
||||
from_name VARCHAR(100),
|
||||
to_addresses TEXT NOT NULL,
|
||||
cc_addresses TEXT,
|
||||
bcc_addresses TEXT,
|
||||
subject VARCHAR(500) NOT NULL,
|
||||
body_html TEXT,
|
||||
body_text TEXT,
|
||||
sent_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
thread_id VARCHAR(250),
|
||||
email_template_id INTEGER,
|
||||
sequence_enrollment_id INTEGER,
|
||||
direction INTEGER NOT NULL,
|
||||
status INTEGER,
|
||||
is_tracked BOOLEAN DEFAULT TRUE,
|
||||
open_count INTEGER DEFAULT 0,
|
||||
click_count INTEGER DEFAULT 0,
|
||||
first_opened_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
first_clicked_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
bounce_type INTEGER,
|
||||
is_spam_complaint BOOLEAN DEFAULT FALSE,
|
||||
reply_to_email_id INTEGER,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_email_message_activity_id ON crm_email_message (activity_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_email_message_thread_id ON crm_email_message (thread_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_email_message_email_template_id ON crm_email_message (email_template_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_email_message_sequence_enrollment_id ON crm_email_message (sequence_enrollment_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_email_message_reply_to_email_id ON crm_email_message (reply_to_email_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_email_sequence (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(250) NOT NULL,
|
||||
description TEXT,
|
||||
owner_user_id TEXT NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
total_steps INTEGER,
|
||||
business_days_only BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
send_window_start_hour INTEGER,
|
||||
send_window_end_hour INTEGER,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_email_sequence_owner_user_id ON crm_email_sequence (owner_user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_email_template (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(250) NOT NULL,
|
||||
subject VARCHAR(500) NOT NULL,
|
||||
body_html TEXT NOT NULL,
|
||||
body_text TEXT,
|
||||
category VARCHAR(100),
|
||||
owner_user_id TEXT NOT NULL,
|
||||
is_shared BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_email_template_owner_user_id ON crm_email_template (owner_user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_external_mapping (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
entity_type INTEGER NOT NULL,
|
||||
entity_id INTEGER NOT NULL,
|
||||
external_system VARCHAR(100) NOT NULL,
|
||||
external_id VARCHAR(500) NOT NULL,
|
||||
last_sync_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
sync_status INTEGER,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_external_mapping_1 UNIQUE (entity_type, entity_id, external_system)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_external_mapping_entity_id ON crm_external_mapping (entity_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_external_mapping_external_id ON crm_external_mapping (external_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_form_submission (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
form_type INTEGER NOT NULL,
|
||||
email VARCHAR(250) NOT NULL,
|
||||
first_name VARCHAR(100),
|
||||
last_name VARCHAR(100),
|
||||
company VARCHAR(250),
|
||||
phone VARCHAR(50),
|
||||
message TEXT,
|
||||
source_url VARCHAR(1000),
|
||||
utm_source VARCHAR(250),
|
||||
utm_medium VARCHAR(250),
|
||||
utm_campaign VARCHAR(250),
|
||||
utm_term VARCHAR(250),
|
||||
utm_content VARCHAR(250),
|
||||
referrer VARCHAR(1000),
|
||||
contact_id INTEGER,
|
||||
conversion_status INTEGER DEFAULT 1 NOT NULL,
|
||||
converted_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
converted_by_user_id TEXT,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent VARCHAR(500),
|
||||
metadata TEXT,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_form_submission_contact_id ON crm_form_submission (contact_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_form_submission_converted_by_user_id ON crm_form_submission (converted_by_user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_industry (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_industry_1 UNIQUE (name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_lead_score_rule (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(250) NOT NULL,
|
||||
description TEXT,
|
||||
entity_type INTEGER NOT NULL,
|
||||
field_path VARCHAR(250) NOT NULL,
|
||||
operator INTEGER NOT NULL,
|
||||
field_value VARCHAR(500),
|
||||
score_adjustment INTEGER NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_lead_source (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_lead_source_1 UNIQUE (name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_lifecycle_stage (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
description VARCHAR(500),
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_lifecycle_stage_1 UNIQUE (name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_pipeline (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description VARCHAR(500),
|
||||
is_default BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_pipeline_1 UNIQUE (name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_pipeline_stage (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
pipeline_id INTEGER NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
probability_pct INTEGER DEFAULT 0 NOT NULL,
|
||||
rot_days INTEGER,
|
||||
required_fields_json TEXT,
|
||||
is_closed_won BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
is_closed_lost BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_pipeline_stage_1 UNIQUE (pipeline_id, name),
|
||||
CONSTRAINT uq_crm_pipeline_stage_2 UNIQUE (pipeline_id, sort_order)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_pipeline_stage_pipeline_id ON crm_pipeline_stage (pipeline_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_product (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(250) NOT NULL,
|
||||
sku VARCHAR(100),
|
||||
description TEXT,
|
||||
unit_price DECIMAL(20,6) NOT NULL,
|
||||
currency_code VARCHAR(3) DEFAULT 'USD',
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_product_1 UNIQUE (sku)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_sales_goal (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
pipeline_id INTEGER,
|
||||
period_type INTEGER NOT NULL,
|
||||
period_start DATE NOT NULL,
|
||||
period_end DATE NOT NULL,
|
||||
target_amount DECIMAL(20,6) NOT NULL,
|
||||
currency_code VARCHAR(3) DEFAULT 'USD',
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_sales_goal_1 UNIQUE (user_id, pipeline_id, period_type, period_start)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_sales_goal_user_id ON crm_sales_goal (user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_sales_goal_pipeline_id ON crm_sales_goal (pipeline_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_sequence_enrollment (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
sequence_id INTEGER NOT NULL,
|
||||
contact_id INTEGER NOT NULL,
|
||||
deal_id INTEGER,
|
||||
current_step_number INTEGER DEFAULT 0 NOT NULL,
|
||||
status INTEGER NOT NULL,
|
||||
enrolled_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
enrolled_by_user_id TEXT NOT NULL,
|
||||
completed_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
unenrolled_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
unenroll_reason VARCHAR(250),
|
||||
next_step_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
failure_count INTEGER DEFAULT 0,
|
||||
last_failure_message VARCHAR(1000),
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_sequence_enrollment_1 UNIQUE (sequence_id, contact_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_sequence_enrollment_sequence_id ON crm_sequence_enrollment (sequence_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_sequence_enrollment_contact_id ON crm_sequence_enrollment (contact_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_sequence_enrollment_deal_id ON crm_sequence_enrollment (deal_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_sequence_enrollment_enrolled_by_user_id ON crm_sequence_enrollment (enrolled_by_user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_sequence_step (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
sequence_id INTEGER NOT NULL,
|
||||
step_number INTEGER NOT NULL,
|
||||
step_type INTEGER NOT NULL,
|
||||
delay_days INTEGER DEFAULT 0 NOT NULL,
|
||||
delay_hours INTEGER DEFAULT 0,
|
||||
email_template_id INTEGER,
|
||||
task_subject VARCHAR(500),
|
||||
task_description TEXT,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_sequence_step_1 UNIQUE (sequence_id, step_number)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_sequence_step_sequence_id ON crm_sequence_step (sequence_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crm_sequence_step_email_template_id ON crm_sequence_step (email_template_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS crm_win_loss_reason (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
type INTEGER NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
create_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT uq_crm_win_loss_reason_1 UNIQUE (name)
|
||||
);
|
||||
@@ -0,0 +1,295 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Apache Maven Wrapper startup batch script, version 3.3.4
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
set -euf
|
||||
[ "${MVNW_VERBOSE-}" != debug ] || set -x
|
||||
|
||||
# OS specific support.
|
||||
native_path() { printf %s\\n "$1"; }
|
||||
case "$(uname)" in
|
||||
CYGWIN* | MINGW*)
|
||||
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
|
||||
native_path() { cygpath --path --windows "$1"; }
|
||||
;;
|
||||
esac
|
||||
|
||||
# set JAVACMD and JAVACCMD
|
||||
set_java_home() {
|
||||
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
|
||||
if [ -n "${JAVA_HOME-}" ]; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACCMD="$JAVA_HOME/jre/sh/javac"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACCMD="$JAVA_HOME/bin/javac"
|
||||
|
||||
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
|
||||
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
|
||||
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
JAVACMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v java
|
||||
)" || :
|
||||
JAVACCMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v javac
|
||||
)" || :
|
||||
|
||||
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
|
||||
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# hash string like Java String::hashCode
|
||||
hash_string() {
|
||||
str="${1:-}" h=0
|
||||
while [ -n "$str" ]; do
|
||||
char="${str%"${str#?}"}"
|
||||
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
|
||||
str="${str#?}"
|
||||
done
|
||||
printf %x\\n $h
|
||||
}
|
||||
|
||||
verbose() { :; }
|
||||
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
|
||||
|
||||
die() {
|
||||
printf %s\\n "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
trim() {
|
||||
# MWRAPPER-139:
|
||||
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||
# Needed for removing poorly interpreted newline sequences when running in more
|
||||
# exotic environments such as mingw bash on Windows.
|
||||
printf "%s" "${1}" | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
scriptDir="$(dirname "$0")"
|
||||
scriptName="$(basename "$0")"
|
||||
|
||||
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||
while IFS="=" read -r key value; do
|
||||
case "${key-}" in
|
||||
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||
esac
|
||||
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||
|
||||
case "${distributionUrl##*/}" in
|
||||
maven-mvnd-*bin.*)
|
||||
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
|
||||
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
|
||||
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
|
||||
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
|
||||
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
|
||||
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
|
||||
*)
|
||||
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
|
||||
distributionPlatform=linux-amd64
|
||||
;;
|
||||
esac
|
||||
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||
;;
|
||||
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
||||
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
|
||||
esac
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
distributionUrlNameMain="${distributionUrlName%.*}"
|
||||
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
|
||||
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
|
||||
|
||||
exec_maven() {
|
||||
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
|
||||
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
|
||||
}
|
||||
|
||||
if [ -d "$MAVEN_HOME" ]; then
|
||||
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
exec_maven "$@"
|
||||
fi
|
||||
|
||||
case "${distributionUrl-}" in
|
||||
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
|
||||
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
|
||||
esac
|
||||
|
||||
# prepare tmp dir
|
||||
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
|
||||
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
|
||||
trap clean HUP INT TERM EXIT
|
||||
else
|
||||
die "cannot create temp dir"
|
||||
fi
|
||||
|
||||
mkdir -p -- "${MAVEN_HOME%/*}"
|
||||
|
||||
# Download and Install Apache Maven
|
||||
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
verbose "Downloading from: $distributionUrl"
|
||||
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
# select .zip or .tar.gz
|
||||
if ! command -v unzip >/dev/null; then
|
||||
distributionUrl="${distributionUrl%.zip}.tar.gz"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
fi
|
||||
|
||||
# verbose opt
|
||||
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
|
||||
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
|
||||
|
||||
# normalize http auth
|
||||
case "${MVNW_PASSWORD:+has-password}" in
|
||||
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
esac
|
||||
|
||||
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
|
||||
verbose "Found wget ... using wget"
|
||||
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
|
||||
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
|
||||
verbose "Found curl ... using curl"
|
||||
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
|
||||
elif set_java_home; then
|
||||
verbose "Falling back to use Java to download"
|
||||
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
|
||||
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
cat >"$javaSource" <<-END
|
||||
public class Downloader extends java.net.Authenticator
|
||||
{
|
||||
protected java.net.PasswordAuthentication getPasswordAuthentication()
|
||||
{
|
||||
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
|
||||
}
|
||||
public static void main( String[] args ) throws Exception
|
||||
{
|
||||
setDefault( new Downloader() );
|
||||
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
|
||||
}
|
||||
}
|
||||
END
|
||||
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
|
||||
verbose " - Compiling Downloader.java ..."
|
||||
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
|
||||
verbose " - Running Downloader.java ..."
|
||||
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
|
||||
fi
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
if [ -n "${distributionSha256Sum-}" ]; then
|
||||
distributionSha256Result=false
|
||||
if [ "$MVN_CMD" = mvnd.sh ]; then
|
||||
echo "Checksum validation is not supported for maven-mvnd." >&2
|
||||
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
elif command -v sha256sum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
elif command -v shasum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
else
|
||||
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
|
||||
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ $distributionSha256Result = false ]; then
|
||||
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
|
||||
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# unzip and move
|
||||
if command -v unzip >/dev/null; then
|
||||
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
|
||||
else
|
||||
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||
fi
|
||||
|
||||
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||
actualDistributionDir=""
|
||||
|
||||
# First try the expected directory name (for regular distributions)
|
||||
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
|
||||
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
|
||||
actualDistributionDir="$distributionUrlNameMain"
|
||||
fi
|
||||
fi
|
||||
|
||||
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||
if [ -z "$actualDistributionDir" ]; then
|
||||
# enable globbing to iterate over items
|
||||
set +f
|
||||
for dir in "$TMP_DOWNLOAD_DIR"/*; do
|
||||
if [ -d "$dir" ]; then
|
||||
if [ -f "$dir/bin/$MVN_CMD" ]; then
|
||||
actualDistributionDir="$(basename "$dir")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
set -f
|
||||
fi
|
||||
|
||||
if [ -z "$actualDistributionDir" ]; then
|
||||
verbose "Contents of $TMP_DOWNLOAD_DIR:"
|
||||
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
|
||||
die "Could not find Maven distribution directory in extracted archive"
|
||||
fi
|
||||
|
||||
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
|
||||
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||
|
||||
clean || :
|
||||
exec_maven "$@"
|
||||
@@ -0,0 +1,189 @@
|
||||
<# : batch portion
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Apache Maven Wrapper startup batch script, version 3.3.4
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
||||
@SET __MVNW_CMD__=
|
||||
@SET __MVNW_ERROR__=
|
||||
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||
@SET PSModulePath=
|
||||
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||
)
|
||||
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||
@SET __MVNW_PSMODULEP_SAVE=
|
||||
@SET __MVNW_ARG0_NAME__=
|
||||
@SET MVNW_USERNAME=
|
||||
@SET MVNW_PASSWORD=
|
||||
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
|
||||
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||
@GOTO :EOF
|
||||
: end batch / begin powershell #>
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
if ($env:MVNW_VERBOSE -eq "true") {
|
||||
$VerbosePreference = "Continue"
|
||||
}
|
||||
|
||||
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||
if (!$distributionUrl) {
|
||||
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||
}
|
||||
|
||||
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
||||
"maven-mvnd-*" {
|
||||
$USE_MVND = $true
|
||||
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
||||
$MVN_CMD = "mvnd.cmd"
|
||||
break
|
||||
}
|
||||
default {
|
||||
$USE_MVND = $false
|
||||
$MVN_CMD = $script -replace '^mvnw','mvn'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
if ($env:MVNW_REPOURL) {
|
||||
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
||||
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
|
||||
}
|
||||
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||
|
||||
$MAVEN_M2_PATH = "$HOME/.m2"
|
||||
if ($env:MAVEN_USER_HOME) {
|
||||
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
|
||||
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
|
||||
}
|
||||
|
||||
$MAVEN_WRAPPER_DISTS = $null
|
||||
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
|
||||
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
|
||||
} else {
|
||||
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
|
||||
}
|
||||
|
||||
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
|
||||
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
||||
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||
|
||||
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||
exit $?
|
||||
}
|
||||
|
||||
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
||||
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
||||
}
|
||||
|
||||
# prepare tmp dir
|
||||
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
||||
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
||||
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
||||
trap {
|
||||
if ($TMP_DOWNLOAD_DIR.Exists) {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
||||
|
||||
# Download and Install Apache Maven
|
||||
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
Write-Verbose "Downloading from: $distributionUrl"
|
||||
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
||||
}
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||
if ($distributionSha256Sum) {
|
||||
if ($USE_MVND) {
|
||||
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||
}
|
||||
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
||||
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
||||
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
||||
}
|
||||
}
|
||||
|
||||
# unzip and move
|
||||
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
||||
|
||||
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||
$actualDistributionDir = ""
|
||||
|
||||
# First try the expected directory name (for regular distributions)
|
||||
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
|
||||
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
|
||||
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
|
||||
$actualDistributionDir = $distributionUrlNameMain
|
||||
}
|
||||
|
||||
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||
if (!$actualDistributionDir) {
|
||||
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
|
||||
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
|
||||
if (Test-Path -Path $testPath -PathType Leaf) {
|
||||
$actualDistributionDir = $_.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$actualDistributionDir) {
|
||||
Write-Error "Could not find Maven distribution directory in extracted archive"
|
||||
}
|
||||
|
||||
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
|
||||
try {
|
||||
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||
} catch {
|
||||
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
||||
Write-Error "fail to move MAVEN_HOME"
|
||||
}
|
||||
} finally {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||
@@ -22,13 +22,18 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<properties>
|
||||
<revision>0.1.0</revision>
|
||||
<revision>0.2.0-SNAPSHOT</revision>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<jacoco.version>0.8.15</jacoco.version>
|
||||
<qbit.guardrails.enforce>true</qbit.guardrails.enforce>
|
||||
<qbit.guardrails.strictUiApiRoutes>true</qbit.guardrails.strictUiApiRoutes>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>com.kingsrook</groupId>
|
||||
<artifactId>qbit-build-parent</artifactId>
|
||||
<version>1.4.0</version>
|
||||
<version>2.1.2</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.kingsrook.qbits</groupId>
|
||||
@@ -82,7 +87,7 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>2.2.220</version>
|
||||
<version>2.3.232</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -95,9 +100,101 @@
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.27.3</version>
|
||||
<version>3.27.7</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<configLocation>checkstyle/config.xml</configLocation>
|
||||
<includeTestSourceDirectory>true</includeTestSourceDirectory>
|
||||
<violationSeverity>warning</violationSeverity>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>checkstyle</id>
|
||||
<phase>validate</phase>
|
||||
<goals><goal>check</goal></goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>${jacoco.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>check</id>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<rule>
|
||||
<element>BUNDLE</element>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>INSTRUCTION</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.70</minimum>
|
||||
</limit>
|
||||
<limit>
|
||||
<counter>CLASS</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.90</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<!-- Built against qqq 4.1 (QBitMetaDataProducer). Until qbit-build-parent -->
|
||||
<!-- imports the released 4.1 BOM, this profile is active by default and -->
|
||||
<!-- imports qqq-bom-pom 4.1.0-RC.6 ahead of the parent's 4.0.0 BOM. -->
|
||||
<profile>
|
||||
<id>qqq-snapshot</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<qqq.snapshot.version>4.1.0-RC.6</qqq.snapshot.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-bom-pom</artifactId>
|
||||
<version>${qqq.snapshot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -1,37 +1,54 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Configuration for the CRM QBit.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitConfig;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QBitConfig for the CRM QBit. Holds backend name, security configuration,
|
||||
** table metadata customizer, and companion QBit namespace references.
|
||||
** table metadata customizer and backend column naming.
|
||||
*******************************************************************************/
|
||||
public class CrmQBitConfig implements QBitConfig
|
||||
{
|
||||
public static final CrmQBitConfig DEFAULT_CONFIG = new CrmQBitConfig();
|
||||
|
||||
private String defaultBackendNameForTables;
|
||||
private String tableNamePrefix;
|
||||
private Boolean inferBackendNames = true;
|
||||
private MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer;
|
||||
private List<QFieldMetaData> securityFields;
|
||||
private List<RecordSecurityLock> recordSecurityLocks;
|
||||
|
||||
private String quickSearchQBitNamespace;
|
||||
private String webhooksQBitNamespace;
|
||||
private String workflowsQBitNamespace;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -110,27 +127,6 @@ public class CrmQBitConfig implements QBitConfig
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableNamePrefix
|
||||
*******************************************************************************/
|
||||
public String getTableNamePrefix()
|
||||
{
|
||||
return (this.tableNamePrefix);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableNamePrefix
|
||||
*******************************************************************************/
|
||||
public CrmQBitConfig withTableNamePrefix(String tableNamePrefix)
|
||||
{
|
||||
this.tableNamePrefix = tableNamePrefix;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for securityFields
|
||||
*******************************************************************************/
|
||||
@@ -174,63 +170,21 @@ public class CrmQBitConfig implements QBitConfig
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for quickSearchQBitNamespace
|
||||
** Whether to infer snake_case database column names.
|
||||
*******************************************************************************/
|
||||
public String getQuickSearchQBitNamespace()
|
||||
public Boolean getInferBackendNames()
|
||||
{
|
||||
return (this.quickSearchQBitNamespace);
|
||||
return (this.inferBackendNames);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for quickSearchQBitNamespace
|
||||
** Configure automatic database column naming.
|
||||
*******************************************************************************/
|
||||
public CrmQBitConfig withQuickSearchQBitNamespace(String quickSearchQBitNamespace)
|
||||
public CrmQBitConfig withInferBackendNames(Boolean inferBackendNames)
|
||||
{
|
||||
this.quickSearchQBitNamespace = quickSearchQBitNamespace;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for webhooksQBitNamespace
|
||||
*******************************************************************************/
|
||||
public String getWebhooksQBitNamespace()
|
||||
{
|
||||
return (this.webhooksQBitNamespace);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for webhooksQBitNamespace
|
||||
*******************************************************************************/
|
||||
public CrmQBitConfig withWebhooksQBitNamespace(String webhooksQBitNamespace)
|
||||
{
|
||||
this.webhooksQBitNamespace = webhooksQBitNamespace;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for workflowsQBitNamespace
|
||||
*******************************************************************************/
|
||||
public String getWorkflowsQBitNamespace()
|
||||
{
|
||||
return (this.workflowsQBitNamespace);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for workflowsQBitNamespace
|
||||
*******************************************************************************/
|
||||
public CrmQBitConfig withWorkflowsQBitNamespace(String workflowsQBitNamespace)
|
||||
{
|
||||
this.workflowsQBitNamespace = workflowsQBitNamespace;
|
||||
this.inferBackendNames = inferBackendNames;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,61 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QBitMetaDataProducer for the CRM QBit.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import com.kingsrook.qbits.crm.activities.customizers.ActivityLastDateCustomizer;
|
||||
import com.kingsrook.qbits.crm.activities.model.Activity;
|
||||
import com.kingsrook.qbits.crm.core.customizers.FormSubmissionAutoLinkCustomizer;
|
||||
import com.kingsrook.qbits.crm.core.model.FormSubmission;
|
||||
import com.kingsrook.qbits.crm.audit.customizers.CrmAuditLogCustomizer;
|
||||
import com.kingsrook.qbits.crm.core.model.Company;
|
||||
import com.kingsrook.qbits.crm.core.model.Contact;
|
||||
import com.kingsrook.qbits.crm.core.model.FormSubmission;
|
||||
import com.kingsrook.qbits.crm.deals.customizers.DealInitializationCustomizer;
|
||||
import com.kingsrook.qbits.crm.deals.customizers.DealProductRecalculationCustomizer;
|
||||
import com.kingsrook.qbits.crm.deals.customizers.DealStageChangeCustomizer;
|
||||
import com.kingsrook.qbits.crm.deals.model.Deal;
|
||||
import com.kingsrook.qbits.crm.deals.model.DealProduct;
|
||||
import com.kingsrook.qbits.crm.security.CrmPermissions;
|
||||
import com.kingsrook.qbits.crm.widgets.CrmWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.MultiCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceWithProperties;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaDataProducer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
@@ -39,7 +69,7 @@ public class CrmQBitProducer implements QBitMetaDataProducer<CrmQBitConfig>
|
||||
{
|
||||
public static final String GROUP_ID = "com.kingsrook.qbits";
|
||||
public static final String ARTIFACT_ID = "crm";
|
||||
public static final String VERSION = "0.1.0";
|
||||
public static final String VERSION = "0.2.0-SNAPSHOT";
|
||||
|
||||
private CrmQBitConfig qBitConfig;
|
||||
|
||||
@@ -67,12 +97,48 @@ public class CrmQBitProducer implements QBitMetaDataProducer<CrmQBitConfig>
|
||||
@Override
|
||||
public void postProduceActions(MetaDataProducerMultiOutput metaDataProducerMultiOutput, QInstance qInstance) throws QException
|
||||
{
|
||||
for(QJoinMetaData join : metaDataProducerMultiOutput.getEach(QJoinMetaData.class))
|
||||
{
|
||||
join.setName(snakeCase(join.getName()));
|
||||
}
|
||||
for(QWidgetMetaData widget : metaDataProducerMultiOutput.getEach(QWidgetMetaData.class))
|
||||
{
|
||||
widget.setName(snakeCase(widget.getName()));
|
||||
widget.setPermissionRules(CrmPermissions.hasAccessRules(CrmPermissions.BASE));
|
||||
if(widget.getDefaultValues() != null && widget.getDefaultValues().get("joinName") instanceof String joinName)
|
||||
{
|
||||
widget.withDefaultValue("joinName", snakeCase(joinName));
|
||||
}
|
||||
}
|
||||
|
||||
for(QProcessMetaData process : metaDataProducerMultiOutput.getEach(QProcessMetaData.class))
|
||||
{
|
||||
process.setPermissionRules(CrmPermissions.processRules(process.getName()));
|
||||
process.setSourceQBitName(getQBitMetaData().getName());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// iterate tables produced by this QBit and register customizers //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
for(QTableMetaData table : metaDataProducerMultiOutput.getEach(QTableMetaData.class))
|
||||
{
|
||||
String tableName = table.getName();
|
||||
table.setPermissionRules(CrmPermissions.tableRules(tableName));
|
||||
table.setSourceQBitName(getQBitMetaData().getName());
|
||||
if(table.getSections() != null)
|
||||
{
|
||||
table.getSections().forEach(section ->
|
||||
{
|
||||
if(section.getWidgetName() != null)
|
||||
{
|
||||
section.setWidgetName(snakeCase(section.getWidgetName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
if(Boolean.TRUE.equals(qBitConfig.getInferBackendNames()))
|
||||
{
|
||||
QInstanceEnricher.setInferredFieldBackendNames(table);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// wire audit log on tracked entities //
|
||||
@@ -80,10 +146,7 @@ public class CrmQBitProducer implements QBitMetaDataProducer<CrmQBitConfig>
|
||||
if(tableName.equals(Contact.TABLE_NAME) || tableName.equals(Company.TABLE_NAME)
|
||||
|| tableName.equals(Deal.TABLE_NAME) || tableName.equals(Activity.TABLE_NAME))
|
||||
{
|
||||
QCodeReference auditRef = new QCodeReference(CrmAuditLogCustomizer.class);
|
||||
addOrComposeCustomizer(table, TableCustomizers.POST_INSERT_RECORD, auditRef);
|
||||
addOrComposeCustomizer(table, TableCustomizers.POST_UPDATE_RECORD, auditRef);
|
||||
addOrComposeCustomizer(table, TableCustomizers.POST_DELETE_RECORD, auditRef);
|
||||
table.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
@@ -209,4 +272,28 @@ public class CrmQBitProducer implements QBitMetaDataProducer<CrmQBitConfig>
|
||||
return (this);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Normalize generated metadata names as well as explicit CRM names.
|
||||
*******************************************************************************/
|
||||
private static String snakeCase(String name)
|
||||
{
|
||||
return (name.replaceAll("([a-z0-9])([A-Z])", "$1_$2").toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Add widget provenance after the standard package-discovered production.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public MetaDataProducerMultiOutput produce(QInstance qInstance) throws QException
|
||||
{
|
||||
MetaDataProducerMultiOutput produced = QBitMetaDataProducer.super.produce(qInstance);
|
||||
MetaDataProducerMultiOutput output = new MetaDataProducerMultiOutput();
|
||||
for(MetaDataProducerOutput item : produced.getEach(MetaDataProducerOutput.class))
|
||||
{
|
||||
output.add(item instanceof QWidgetMetaData widget
|
||||
? new CrmWidgetMetaData(widget).withSourceQBitName(getQBitMetaData().getName()) : item);
|
||||
}
|
||||
return (output);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,29 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class for null-safe session user ID resolution. Used by processes
|
||||
** and customizers that need the current user ID for audit logging.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm;
|
||||
|
||||
|
||||
|
||||
+22
@@ -1,8 +1,30 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** POST_INSERT table customizer that propagates lastActivityDate and
|
||||
** lastContactedDate to linked Contact, Company, and Deal records whenever
|
||||
** a new Activity is created.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.activities.customizers;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for the crm_activity table.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.activities.model;
|
||||
|
||||
|
||||
@@ -57,7 +79,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
)
|
||||
public class Activity extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmActivity";
|
||||
public static final String TABLE_NAME = "crm_activity";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for the crm_activity_outcome table.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.activities.model;
|
||||
|
||||
|
||||
@@ -30,7 +52,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
)
|
||||
public class ActivityOutcome extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmActivityOutcome";
|
||||
public static final String TABLE_NAME = "crm_activity_outcome";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for the crm_activity_participant table.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.activities.model;
|
||||
|
||||
|
||||
@@ -29,7 +51,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
)
|
||||
public class ActivityParticipant extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmActivityParticipant";
|
||||
public static final String TABLE_NAME = "crm_activity_participant";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for the crm_activity_type table.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.activities.model;
|
||||
|
||||
|
||||
@@ -34,7 +56,7 @@ import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage
|
||||
)
|
||||
public class ActivityType extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmActivityType";
|
||||
public static final String TABLE_NAME = "crm_activity_type";
|
||||
|
||||
|
||||
|
||||
|
||||
+23
-1
@@ -1,7 +1,29 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Scheduled process that fires reminders for activities with isReminderSet
|
||||
** = true and reminderDate <= now. Clears the reminder flag after processing.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.activities.processes;
|
||||
|
||||
|
||||
@@ -37,7 +59,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
*******************************************************************************/
|
||||
public class FireRemindersProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "fireReminders";
|
||||
public static final String NAME = "crm_fire_reminders";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to log a CRM activity. Creates a single crm_activity record with
|
||||
** the provided fields and sets ownerUserId from the current session user.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.activities.processes;
|
||||
|
||||
|
||||
@@ -16,11 +38,11 @@ import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
@@ -37,7 +59,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
*******************************************************************************/
|
||||
public class LogActivityProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "logActivity";
|
||||
public static final String NAME = "crm_log_activity";
|
||||
|
||||
|
||||
|
||||
|
||||
+23
-1
@@ -1,8 +1,30 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Scheduled process that finds overdue tasks and marks them with a system
|
||||
** note. In production this would fire notifications; for now it logs a
|
||||
** message on each overdue activity.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.activities.processes;
|
||||
|
||||
|
||||
@@ -38,7 +60,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
*******************************************************************************/
|
||||
public class ProcessOverdueTasksProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "processOverdueTasks";
|
||||
public static final String NAME = "crm_process_overdue_tasks";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,290 +0,0 @@
|
||||
/*******************************************************************************
|
||||
** POST_INSERT, POST_UPDATE, and POST_DELETE table customizer that creates
|
||||
** immutable audit log entries for tracked CRM entities.
|
||||
**
|
||||
** Registered globally on tracked tables (contact, company, deal, activity)
|
||||
** via MultiCustomizer in CrmQBitProducer.postProduceActions.
|
||||
*******************************************************************************/
|
||||
package com.kingsrook.qbits.crm.audit.customizers;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qbits.crm.audit.model.AuditLog;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmAuditAction;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEntityType;
|
||||
import com.kingsrook.qbits.crm.CrmSessionUtils;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Audit log customizer that creates crm_audit_log entries for every insert,
|
||||
** update, and delete on tracked entities. For updates, field-level change
|
||||
** tracking captures old and new values, with special detection for owner
|
||||
** and pipeline stage changes.
|
||||
*******************************************************************************/
|
||||
public class CrmAuditLogCustomizer implements TableCustomizerInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(CrmAuditLogCustomizer.class);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// map of table names to CrmEntityType IDs for tracked entities //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
private static final Map<String, Integer> TABLE_TO_ENTITY_TYPE = new HashMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
TABLE_TO_ENTITY_TYPE.put("crmContact", CrmEntityType.CONTACT.getPossibleValueId());
|
||||
TABLE_TO_ENTITY_TYPE.put("crmCompany", CrmEntityType.COMPANY.getPossibleValueId());
|
||||
TABLE_TO_ENTITY_TYPE.put("crmDeal", CrmEntityType.DEAL.getPossibleValueId());
|
||||
TABLE_TO_ENTITY_TYPE.put("crmActivity", CrmEntityType.ACTIVITY.getPossibleValueId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Post-insert: log a CREATED action for each inserted record.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
|
||||
{
|
||||
String tableName = insertInput.getTableName();
|
||||
String userId = getCurrentUserId();
|
||||
|
||||
List<QRecord> auditRecords = new ArrayList<>();
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(record.getErrors() != null && !record.getErrors().isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer entityId = ValueUtils.getValueAsInteger(record.getValue("id"));
|
||||
if(entityId == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auditRecords.add(buildAuditRecord(tableName, entityId, CrmAuditAction.CREATED, userId,
|
||||
null, null, null, "Record created"));
|
||||
}
|
||||
|
||||
insertAuditRecords(auditRecords);
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Post-update: log an UPDATED action per changed field, with special
|
||||
** detection for OWNER_CHANGED and STAGE_CHANGED actions.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
String tableName = updateInput.getTableName();
|
||||
String userId = getCurrentUserId();
|
||||
|
||||
Optional<Map<Serializable, QRecord>> oldRecordMap = oldRecordListToMap(
|
||||
updateInput.getTable().getPrimaryKeyField(), oldRecordList);
|
||||
|
||||
if(oldRecordMap.isEmpty())
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// without old records, we cannot determine what changed -- skip logging //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
return (records);
|
||||
}
|
||||
|
||||
List<QRecord> auditRecords = new ArrayList<>();
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(record.getErrors() != null && !record.getErrors().isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Serializable primaryKey = record.getValue(updateInput.getTable().getPrimaryKeyField());
|
||||
Integer entityId = ValueUtils.getValueAsInteger(primaryKey);
|
||||
QRecord oldRecord = oldRecordMap.get().get(primaryKey);
|
||||
|
||||
if(entityId == null || oldRecord == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for(String fieldName : record.getValues().keySet())
|
||||
{
|
||||
Serializable oldVal = oldRecord.getValue(fieldName);
|
||||
Serializable newVal = record.getValue(fieldName);
|
||||
|
||||
if(!Objects.equals(oldVal, newVal))
|
||||
{
|
||||
String oldStr = oldVal == null ? null : String.valueOf(oldVal);
|
||||
String newStr = newVal == null ? null : String.valueOf(newVal);
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// determine the specific action for special tracked fields //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
CrmAuditAction auditAction = CrmAuditAction.UPDATED;
|
||||
if("ownerUserId".equals(fieldName))
|
||||
{
|
||||
auditAction = CrmAuditAction.OWNER_CHANGED;
|
||||
}
|
||||
else if("pipelineStageId".equals(fieldName))
|
||||
{
|
||||
auditAction = CrmAuditAction.STAGE_CHANGED;
|
||||
}
|
||||
|
||||
auditRecords.add(buildAuditRecord(tableName, entityId, auditAction, userId,
|
||||
fieldName, oldStr, newStr, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insertAuditRecords(auditRecords);
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Post-delete: log a DELETED action for each deleted record.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
|
||||
{
|
||||
String tableName = deleteInput.getTableName();
|
||||
String userId = getCurrentUserId();
|
||||
|
||||
List<QRecord> auditRecords = new ArrayList<>();
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(record.getErrors() != null && !record.getErrors().isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer entityId = ValueUtils.getValueAsInteger(record.getValue("id"));
|
||||
if(entityId == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auditRecords.add(buildAuditRecord(tableName, entityId, CrmAuditAction.DELETED, userId,
|
||||
null, null, null, "Record deleted"));
|
||||
}
|
||||
|
||||
insertAuditRecords(auditRecords);
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Build a single audit log QRecord.
|
||||
*******************************************************************************/
|
||||
private QRecord buildAuditRecord(String tableName, Integer entityId, CrmAuditAction action,
|
||||
String userId, String fieldName, String oldValue,
|
||||
String newValue, String message)
|
||||
{
|
||||
QRecord auditRecord = new QRecord();
|
||||
auditRecord.setValue("entityType", resolveEntityType(tableName));
|
||||
auditRecord.setValue("entityId", entityId);
|
||||
auditRecord.setValue("action", action.getPossibleValueId());
|
||||
auditRecord.setValue("userId", userId);
|
||||
|
||||
if(StringUtils.hasContent(fieldName))
|
||||
{
|
||||
auditRecord.setValue("fieldName", fieldName);
|
||||
}
|
||||
|
||||
if(oldValue != null)
|
||||
{
|
||||
auditRecord.setValue("oldValue", oldValue);
|
||||
}
|
||||
|
||||
if(newValue != null)
|
||||
{
|
||||
auditRecord.setValue("newValue", newValue);
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(message))
|
||||
{
|
||||
auditRecord.setValue("message", message);
|
||||
}
|
||||
|
||||
return (auditRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Resolve a table name to its CrmEntityType integer ID.
|
||||
** Returns the mapped ID if known, or falls back to the table name string
|
||||
** for unrecognized tables (allowing forward-compatibility).
|
||||
*******************************************************************************/
|
||||
private Object resolveEntityType(String tableName)
|
||||
{
|
||||
Integer entityTypeId = TABLE_TO_ENTITY_TYPE.get(tableName);
|
||||
if(entityTypeId != null)
|
||||
{
|
||||
return (entityTypeId);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// fallback: use the table name directly for unrecognized entity types //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
return (tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Insert a batch of audit log records using InsertAction.
|
||||
*******************************************************************************/
|
||||
private void insertAuditRecords(List<QRecord> auditRecords) throws QException
|
||||
{
|
||||
if(auditRecords.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(AuditLog.TABLE_NAME);
|
||||
insertInput.setRecords(auditRecords);
|
||||
new InsertAction().execute(insertInput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error inserting audit log records", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get the current user ID from the QContext session.
|
||||
*******************************************************************************/
|
||||
private String getCurrentUserId()
|
||||
{
|
||||
return (CrmSessionUtils.getCurrentUserId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,481 +0,0 @@
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for the immutable CRM audit log table.
|
||||
*******************************************************************************/
|
||||
package com.kingsrook.qbits.crm.audit.model;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmAuditAction;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEntityType;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreDeleteCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreUpdateCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Immutable append-only audit ledger for tracked CRM entities.
|
||||
** Records field-level changes, stage transitions, owner changes, and lifecycle
|
||||
** events with full before/after values and user attribution.
|
||||
*******************************************************************************/
|
||||
@QMetaDataProducingEntity(
|
||||
producePossibleValueSource = false,
|
||||
produceTableMetaData = true,
|
||||
tableMetaDataCustomizer = AuditLog.TableMetaDataCustomizer.class
|
||||
)
|
||||
public class AuditLog extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmAuditLog";
|
||||
|
||||
|
||||
@QField(isEditable = false, isPrimaryKey = true)
|
||||
private Integer id;
|
||||
|
||||
@QField(isRequired = true, possibleValueSourceName = CrmEntityType.NAME)
|
||||
private Integer entityType;
|
||||
|
||||
@QField(isRequired = true)
|
||||
private Integer entityId;
|
||||
|
||||
@QField(isRequired = true, possibleValueSourceName = CrmAuditAction.NAME)
|
||||
private Integer action;
|
||||
|
||||
@QField(isRequired = true)
|
||||
private String userId;
|
||||
|
||||
@QField(maxLength = 100)
|
||||
private String fieldName;
|
||||
|
||||
@QField()
|
||||
private String oldValue;
|
||||
|
||||
@QField()
|
||||
private String newValue;
|
||||
|
||||
@QField(maxLength = 1000)
|
||||
private String message;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant createDate;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** TableMetaDataCustomizer -- sets icon, record label, sections, and
|
||||
** registers PRE_UPDATE and PRE_DELETE customizers to enforce immutability.
|
||||
***************************************************************************/
|
||||
public static class TableMetaDataCustomizer implements MetaDataCustomizerInterface<QTableMetaData>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QTableMetaData customizeMetaData(QInstance qInstance, QTableMetaData table) throws QException
|
||||
{
|
||||
table
|
||||
.withIcon(new QIcon().withName("history"))
|
||||
.withRecordLabelFormat("%s %s #%s")
|
||||
.withRecordLabelFields("action", "entityType", "entityId")
|
||||
.withSection(SectionFactory.defaultT1("id", "entityType", "entityId", "action"))
|
||||
.withSection(new QFieldSection("details", new QIcon("text_snippet"), Tier.T2,
|
||||
List.of("userId", "fieldName", "oldValue", "newValue", "message")))
|
||||
.withSection(SectionFactory.defaultT3("createDate"));
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// register PRE_UPDATE and PRE_DELETE customizers to enforce immutability //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
table.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(PreventUpdateCustomizer.class));
|
||||
table.withCustomizer(TableCustomizers.PRE_DELETE_RECORD, new QCodeReference(PreventDeleteCustomizer.class));
|
||||
|
||||
return (table);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** PRE_UPDATE customizer that rejects all update attempts on audit log.
|
||||
***************************************************************************/
|
||||
public static class PreventUpdateCustomizer extends AbstractPreUpdateCustomizer
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> apply(List<QRecord> records) throws QException
|
||||
{
|
||||
if("true".equals(QContext.getQSession().getValue("gdprBypass")))
|
||||
{
|
||||
return records;
|
||||
}
|
||||
throw new QUserFacingException("Audit log records cannot be modified or deleted");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** PRE_DELETE customizer that rejects all delete attempts on audit log.
|
||||
***************************************************************************/
|
||||
public static class PreventDeleteCustomizer extends AbstractPreDeleteCustomizer
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> apply(List<QRecord> records) throws QException
|
||||
{
|
||||
if("true".equals(QContext.getQSession().getValue("gdprBypass")))
|
||||
{
|
||||
return records;
|
||||
}
|
||||
throw new QUserFacingException("Audit log records cannot be modified or deleted");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor
|
||||
*******************************************************************************/
|
||||
public AuditLog()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor that takes a QRecord
|
||||
*******************************************************************************/
|
||||
public AuditLog(QRecord record)
|
||||
{
|
||||
populateFromQRecord(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public AuditLog withId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for entityType
|
||||
*******************************************************************************/
|
||||
public Integer getEntityType()
|
||||
{
|
||||
return (this.entityType);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for entityType
|
||||
*******************************************************************************/
|
||||
public void setEntityType(Integer entityType)
|
||||
{
|
||||
this.entityType = entityType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for entityType
|
||||
*******************************************************************************/
|
||||
public AuditLog withEntityType(Integer entityType)
|
||||
{
|
||||
this.entityType = entityType;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for entityId
|
||||
*******************************************************************************/
|
||||
public Integer getEntityId()
|
||||
{
|
||||
return (this.entityId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for entityId
|
||||
*******************************************************************************/
|
||||
public void setEntityId(Integer entityId)
|
||||
{
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for entityId
|
||||
*******************************************************************************/
|
||||
public AuditLog withEntityId(Integer entityId)
|
||||
{
|
||||
this.entityId = entityId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for action
|
||||
*******************************************************************************/
|
||||
public Integer getAction()
|
||||
{
|
||||
return (this.action);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for action
|
||||
*******************************************************************************/
|
||||
public void setAction(Integer action)
|
||||
{
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for action
|
||||
*******************************************************************************/
|
||||
public AuditLog withAction(Integer action)
|
||||
{
|
||||
this.action = action;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for userId
|
||||
*******************************************************************************/
|
||||
public String getUserId()
|
||||
{
|
||||
return (this.userId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for userId
|
||||
*******************************************************************************/
|
||||
public void setUserId(String userId)
|
||||
{
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for userId
|
||||
*******************************************************************************/
|
||||
public AuditLog withUserId(String userId)
|
||||
{
|
||||
this.userId = userId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fieldName
|
||||
*******************************************************************************/
|
||||
public String getFieldName()
|
||||
{
|
||||
return (this.fieldName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for fieldName
|
||||
*******************************************************************************/
|
||||
public void setFieldName(String fieldName)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for fieldName
|
||||
*******************************************************************************/
|
||||
public AuditLog withFieldName(String fieldName)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for oldValue
|
||||
*******************************************************************************/
|
||||
public String getOldValue()
|
||||
{
|
||||
return (this.oldValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for oldValue
|
||||
*******************************************************************************/
|
||||
public void setOldValue(String oldValue)
|
||||
{
|
||||
this.oldValue = oldValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for oldValue
|
||||
*******************************************************************************/
|
||||
public AuditLog withOldValue(String oldValue)
|
||||
{
|
||||
this.oldValue = oldValue;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for newValue
|
||||
*******************************************************************************/
|
||||
public String getNewValue()
|
||||
{
|
||||
return (this.newValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for newValue
|
||||
*******************************************************************************/
|
||||
public void setNewValue(String newValue)
|
||||
{
|
||||
this.newValue = newValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for newValue
|
||||
*******************************************************************************/
|
||||
public AuditLog withNewValue(String newValue)
|
||||
{
|
||||
this.newValue = newValue;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for message
|
||||
*******************************************************************************/
|
||||
public String getMessage()
|
||||
{
|
||||
return (this.message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for message
|
||||
*******************************************************************************/
|
||||
public void setMessage(String message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for message
|
||||
*******************************************************************************/
|
||||
public AuditLog withMessage(String message)
|
||||
{
|
||||
this.message = message;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for createDate
|
||||
*******************************************************************************/
|
||||
public Instant getCreateDate()
|
||||
{
|
||||
return (this.createDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for createDate
|
||||
*******************************************************************************/
|
||||
public AuditLog withCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -1,7 +1,29 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** POST_INSERT customizer for form submissions. Auto-links contactId if a
|
||||
** contact with a matching email address already exists.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.customizers;
|
||||
|
||||
|
||||
|
||||
@@ -1,388 +0,0 @@
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Attachment table
|
||||
*******************************************************************************/
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEntityType;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Attachment table -- polymorphic file attachments linked
|
||||
** to any CRM entity via entityType + entityId. Insert-and-delete-only; no
|
||||
** modifyDate. Carries denormalized clientId for security lock.
|
||||
*******************************************************************************/
|
||||
@QMetaDataProducingEntity(
|
||||
produceTableMetaData = true,
|
||||
tableMetaDataCustomizer = Attachment.TableMetaDataCustomizer.class
|
||||
)
|
||||
public class Attachment extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmAttachment";
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static class TableMetaDataCustomizer implements MetaDataCustomizerInterface<QTableMetaData>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QTableMetaData customizeMetaData(QInstance qInstance, QTableMetaData table) throws QException
|
||||
{
|
||||
table
|
||||
.withIcon(new QIcon().withName("attach_file"))
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("fileName")
|
||||
.withSection(SectionFactory.defaultT1("id", "entityType", "entityId", "fileName"))
|
||||
.withSection(SectionFactory.defaultT2("fileType", "fileSizeBytes", "storageReference", "uploadedByUserId"))
|
||||
.withSection(SectionFactory.defaultT3("createDate"));
|
||||
|
||||
return (table);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@QField(isEditable = false, isPrimaryKey = true)
|
||||
private Integer id;
|
||||
|
||||
@QField(isRequired = true, possibleValueSourceName = CrmEntityType.NAME)
|
||||
private Integer entityType;
|
||||
|
||||
@QField(isRequired = true)
|
||||
private Integer entityId;
|
||||
|
||||
@QField(isRequired = true, maxLength = 500, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||
private String fileName;
|
||||
|
||||
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||
private String fileType;
|
||||
|
||||
@QField()
|
||||
private Long fileSizeBytes;
|
||||
|
||||
@QField(isRequired = true, maxLength = 1000, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||
private String storageReference;
|
||||
|
||||
@QField(isRequired = true)
|
||||
private String uploadedByUserId;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant createDate;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor
|
||||
*******************************************************************************/
|
||||
public Attachment()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor that takes a QRecord
|
||||
*******************************************************************************/
|
||||
public Attachment(QRecord record)
|
||||
{
|
||||
populateFromQRecord(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public Attachment withId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for entityType
|
||||
*******************************************************************************/
|
||||
public Integer getEntityType()
|
||||
{
|
||||
return (this.entityType);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for entityType
|
||||
*******************************************************************************/
|
||||
public void setEntityType(Integer entityType)
|
||||
{
|
||||
this.entityType = entityType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for entityType
|
||||
*******************************************************************************/
|
||||
public Attachment withEntityType(Integer entityType)
|
||||
{
|
||||
this.entityType = entityType;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for entityId
|
||||
*******************************************************************************/
|
||||
public Integer getEntityId()
|
||||
{
|
||||
return (this.entityId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for entityId
|
||||
*******************************************************************************/
|
||||
public void setEntityId(Integer entityId)
|
||||
{
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for entityId
|
||||
*******************************************************************************/
|
||||
public Attachment withEntityId(Integer entityId)
|
||||
{
|
||||
this.entityId = entityId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fileName
|
||||
*******************************************************************************/
|
||||
public String getFileName()
|
||||
{
|
||||
return (this.fileName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for fileName
|
||||
*******************************************************************************/
|
||||
public void setFileName(String fileName)
|
||||
{
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for fileName
|
||||
*******************************************************************************/
|
||||
public Attachment withFileName(String fileName)
|
||||
{
|
||||
this.fileName = fileName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fileType
|
||||
*******************************************************************************/
|
||||
public String getFileType()
|
||||
{
|
||||
return (this.fileType);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for fileType
|
||||
*******************************************************************************/
|
||||
public void setFileType(String fileType)
|
||||
{
|
||||
this.fileType = fileType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for fileType
|
||||
*******************************************************************************/
|
||||
public Attachment withFileType(String fileType)
|
||||
{
|
||||
this.fileType = fileType;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fileSizeBytes
|
||||
*******************************************************************************/
|
||||
public Long getFileSizeBytes()
|
||||
{
|
||||
return (this.fileSizeBytes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for fileSizeBytes
|
||||
*******************************************************************************/
|
||||
public void setFileSizeBytes(Long fileSizeBytes)
|
||||
{
|
||||
this.fileSizeBytes = fileSizeBytes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for fileSizeBytes
|
||||
*******************************************************************************/
|
||||
public Attachment withFileSizeBytes(Long fileSizeBytes)
|
||||
{
|
||||
this.fileSizeBytes = fileSizeBytes;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for storageReference
|
||||
*******************************************************************************/
|
||||
public String getStorageReference()
|
||||
{
|
||||
return (this.storageReference);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for storageReference
|
||||
*******************************************************************************/
|
||||
public void setStorageReference(String storageReference)
|
||||
{
|
||||
this.storageReference = storageReference;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for storageReference
|
||||
*******************************************************************************/
|
||||
public Attachment withStorageReference(String storageReference)
|
||||
{
|
||||
this.storageReference = storageReference;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for uploadedByUserId
|
||||
*******************************************************************************/
|
||||
public String getUploadedByUserId()
|
||||
{
|
||||
return (this.uploadedByUserId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for uploadedByUserId
|
||||
*******************************************************************************/
|
||||
public void setUploadedByUserId(String uploadedByUserId)
|
||||
{
|
||||
this.uploadedByUserId = uploadedByUserId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for uploadedByUserId
|
||||
*******************************************************************************/
|
||||
public Attachment withUploadedByUserId(String uploadedByUserId)
|
||||
{
|
||||
this.uploadedByUserId = uploadedByUserId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for createDate
|
||||
*******************************************************************************/
|
||||
public Instant getCreateDate()
|
||||
{
|
||||
return (this.createDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for createDate
|
||||
*******************************************************************************/
|
||||
public Attachment withCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Company table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
@@ -46,15 +68,12 @@ import com.kingsrook.qqq.backend.core.utils.collections.MutableList;
|
||||
childRecordListWidget = @ChildRecordListWidget(label = "Contacts", enabled = true, maxRows = 50)),
|
||||
@ChildTable(childTableEntityClass = Deal.class, joinFieldName = "companyId",
|
||||
childJoin = @ChildJoin(enabled = true),
|
||||
childRecordListWidget = @ChildRecordListWidget(label = "Deals", enabled = true, maxRows = 25)),
|
||||
@ChildTable(childTableEntityClass = CompanyTag.class, joinFieldName = "companyId",
|
||||
childJoin = @ChildJoin(enabled = true),
|
||||
childRecordListWidget = @ChildRecordListWidget(label = "Tags", enabled = true, maxRows = 50))
|
||||
childRecordListWidget = @ChildRecordListWidget(label = "Deals", enabled = true, maxRows = 25))
|
||||
}
|
||||
)
|
||||
public class Company extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmCompany";
|
||||
public static final String TABLE_NAME = "crm_company";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for CompanyTag table
|
||||
*******************************************************************************/
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for CompanyTag junction table -- many-to-many link between
|
||||
** companies and tags.
|
||||
*******************************************************************************/
|
||||
@QMetaDataProducingEntity(
|
||||
produceTableMetaData = true,
|
||||
tableMetaDataCustomizer = CompanyTag.TableMetaDataCustomizer.class
|
||||
)
|
||||
public class CompanyTag extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmCompanyTag";
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static class TableMetaDataCustomizer implements MetaDataCustomizerInterface<QTableMetaData>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QTableMetaData customizeMetaData(QInstance qInstance, QTableMetaData table) throws QException
|
||||
{
|
||||
table
|
||||
.withIcon(new QIcon().withName("label"))
|
||||
.withRecordLabelFormat("Company %s - Tag %s")
|
||||
.withRecordLabelFields("companyId", "tagId")
|
||||
.withUniqueKey(new UniqueKey("companyId", "tagId"))
|
||||
.withSection(SectionFactory.defaultT1("id", "companyId", "tagId"))
|
||||
.withSection(SectionFactory.defaultT3("createDate"));
|
||||
|
||||
return (table);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@QField(isEditable = false, isPrimaryKey = true)
|
||||
private Integer id;
|
||||
|
||||
@QField(isRequired = true, possibleValueSourceName = Company.TABLE_NAME)
|
||||
private Integer companyId;
|
||||
|
||||
@QField(isRequired = true, possibleValueSourceName = Tag.TABLE_NAME)
|
||||
private Integer tagId;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant createDate;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor
|
||||
*******************************************************************************/
|
||||
public CompanyTag()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor that takes a QRecord
|
||||
*******************************************************************************/
|
||||
public CompanyTag(QRecord record)
|
||||
{
|
||||
populateFromQRecord(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public CompanyTag withId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for companyId
|
||||
*******************************************************************************/
|
||||
public Integer getCompanyId()
|
||||
{
|
||||
return (this.companyId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for companyId
|
||||
*******************************************************************************/
|
||||
public void setCompanyId(Integer companyId)
|
||||
{
|
||||
this.companyId = companyId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for companyId
|
||||
*******************************************************************************/
|
||||
public CompanyTag withCompanyId(Integer companyId)
|
||||
{
|
||||
this.companyId = companyId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tagId
|
||||
*******************************************************************************/
|
||||
public Integer getTagId()
|
||||
{
|
||||
return (this.tagId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tagId
|
||||
*******************************************************************************/
|
||||
public void setTagId(Integer tagId)
|
||||
{
|
||||
this.tagId = tagId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tagId
|
||||
*******************************************************************************/
|
||||
public CompanyTag withTagId(Integer tagId)
|
||||
{
|
||||
this.tagId = tagId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for createDate
|
||||
*******************************************************************************/
|
||||
public Instant getCreateDate()
|
||||
{
|
||||
return (this.createDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for createDate
|
||||
*******************************************************************************/
|
||||
public CompanyTag withCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for ConsentRecord table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
@@ -37,7 +59,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
)
|
||||
public class ConsentRecord extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmConsentRecord";
|
||||
public static final String TABLE_NAME = "crm_consent_record";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Contact table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
@@ -33,7 +55,7 @@ import com.kingsrook.qqq.backend.core.utils.collections.MutableList;
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Contact table -- the primary person record in the CRM.
|
||||
** Contacts are linked to companies, activities, deals, and tags.
|
||||
** Contacts are linked to companies, activities and deals.
|
||||
*******************************************************************************/
|
||||
@QMetaDataProducingEntity(
|
||||
producePossibleValueSource = true,
|
||||
@@ -46,9 +68,6 @@ import com.kingsrook.qqq.backend.core.utils.collections.MutableList;
|
||||
@ChildTable(childTableEntityClass = DealContact.class, joinFieldName = "contactId",
|
||||
childJoin = @ChildJoin(enabled = true),
|
||||
childRecordListWidget = @ChildRecordListWidget(label = "Deals", enabled = true, maxRows = 25)),
|
||||
@ChildTable(childTableEntityClass = ContactTag.class, joinFieldName = "contactId",
|
||||
childJoin = @ChildJoin(enabled = true),
|
||||
childRecordListWidget = @ChildRecordListWidget(label = "Tags", enabled = true, maxRows = 50)),
|
||||
@ChildTable(childTableEntityClass = ConsentRecord.class, joinFieldName = "contactId",
|
||||
childJoin = @ChildJoin(enabled = true),
|
||||
childRecordListWidget = @ChildRecordListWidget(label = "Consent Records", enabled = true, maxRows = 25))
|
||||
@@ -56,7 +75,7 @@ import com.kingsrook.qqq.backend.core.utils.collections.MutableList;
|
||||
)
|
||||
public class Contact extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmContact";
|
||||
public static final String TABLE_NAME = "crm_contact";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for ContactTag table
|
||||
*******************************************************************************/
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for ContactTag junction table -- many-to-many link between
|
||||
** contacts and tags.
|
||||
*******************************************************************************/
|
||||
@QMetaDataProducingEntity(
|
||||
produceTableMetaData = true,
|
||||
tableMetaDataCustomizer = ContactTag.TableMetaDataCustomizer.class
|
||||
)
|
||||
public class ContactTag extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmContactTag";
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static class TableMetaDataCustomizer implements MetaDataCustomizerInterface<QTableMetaData>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QTableMetaData customizeMetaData(QInstance qInstance, QTableMetaData table) throws QException
|
||||
{
|
||||
table
|
||||
.withIcon(new QIcon().withName("label"))
|
||||
.withRecordLabelFormat("Contact %s - Tag %s")
|
||||
.withRecordLabelFields("contactId", "tagId")
|
||||
.withUniqueKey(new UniqueKey("contactId", "tagId"))
|
||||
.withSection(SectionFactory.defaultT1("id", "contactId", "tagId"))
|
||||
.withSection(SectionFactory.defaultT3("createDate"));
|
||||
|
||||
return (table);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@QField(isEditable = false, isPrimaryKey = true)
|
||||
private Integer id;
|
||||
|
||||
@QField(isRequired = true, possibleValueSourceName = Contact.TABLE_NAME)
|
||||
private Integer contactId;
|
||||
|
||||
@QField(isRequired = true, possibleValueSourceName = Tag.TABLE_NAME)
|
||||
private Integer tagId;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant createDate;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor
|
||||
*******************************************************************************/
|
||||
public ContactTag()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor that takes a QRecord
|
||||
*******************************************************************************/
|
||||
public ContactTag(QRecord record)
|
||||
{
|
||||
populateFromQRecord(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public ContactTag withId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for contactId
|
||||
*******************************************************************************/
|
||||
public Integer getContactId()
|
||||
{
|
||||
return (this.contactId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for contactId
|
||||
*******************************************************************************/
|
||||
public void setContactId(Integer contactId)
|
||||
{
|
||||
this.contactId = contactId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for contactId
|
||||
*******************************************************************************/
|
||||
public ContactTag withContactId(Integer contactId)
|
||||
{
|
||||
this.contactId = contactId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tagId
|
||||
*******************************************************************************/
|
||||
public Integer getTagId()
|
||||
{
|
||||
return (this.tagId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tagId
|
||||
*******************************************************************************/
|
||||
public void setTagId(Integer tagId)
|
||||
{
|
||||
this.tagId = tagId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tagId
|
||||
*******************************************************************************/
|
||||
public ContactTag withTagId(Integer tagId)
|
||||
{
|
||||
this.tagId = tagId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for createDate
|
||||
*******************************************************************************/
|
||||
public Instant getCreateDate()
|
||||
{
|
||||
return (this.createDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for createDate
|
||||
*******************************************************************************/
|
||||
public ContactTag withCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Currency table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
@@ -32,7 +54,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
)
|
||||
public class Currency extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmCurrency";
|
||||
public static final String TABLE_NAME = "crm_currency";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for EmailList table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
@@ -30,7 +52,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
)
|
||||
public class EmailList extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmEmailList";
|
||||
public static final String TABLE_NAME = "crm_email_list";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for EmailListMember table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
@@ -33,7 +55,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
)
|
||||
public class EmailListMember extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmEmailListMember";
|
||||
public static final String TABLE_NAME = "crm_email_list_member";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for FormSubmission table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
@@ -35,7 +57,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
)
|
||||
public class FormSubmission extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmFormSubmission";
|
||||
public static final String TABLE_NAME = "crm_form_submission";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Industry table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
@@ -30,7 +52,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
)
|
||||
public class Industry extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmIndustry";
|
||||
public static final String TABLE_NAME = "crm_industry";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for LeadSource table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
@@ -30,7 +52,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
)
|
||||
public class LeadSource extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmLeadSource";
|
||||
public static final String TABLE_NAME = "crm_lead_source";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for LifecycleStage table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
@@ -30,7 +52,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
)
|
||||
public class LifecycleStage extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmLifecycleStage";
|
||||
public static final String TABLE_NAME = "crm_lifecycle_stage";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Tag table
|
||||
*******************************************************************************/
|
||||
package com.kingsrook.qbits.crm.core.model;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Tag table -- freeform tags that can be applied to
|
||||
** contacts, companies, and deals via junction tables.
|
||||
*******************************************************************************/
|
||||
@QMetaDataProducingEntity(
|
||||
producePossibleValueSource = true,
|
||||
produceTableMetaData = true,
|
||||
tableMetaDataCustomizer = Tag.TableMetaDataCustomizer.class
|
||||
)
|
||||
public class Tag extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmTag";
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static class TableMetaDataCustomizer implements MetaDataCustomizerInterface<QTableMetaData>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QTableMetaData customizeMetaData(QInstance qInstance, QTableMetaData table) throws QException
|
||||
{
|
||||
table
|
||||
.withIcon(new QIcon().withName("label"))
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("name")
|
||||
.withUniqueKey(new UniqueKey("name"))
|
||||
.withSection(SectionFactory.defaultT1("id", "name", "color"))
|
||||
.withSection(SectionFactory.defaultT3("createDate", "modifyDate"));
|
||||
|
||||
return (table);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@QField(isEditable = false, isPrimaryKey = true)
|
||||
private Integer id;
|
||||
|
||||
@QField(isRequired = true, maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||
private String name;
|
||||
|
||||
@QField(maxLength = 7, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||
private String color;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant createDate;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant modifyDate;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor
|
||||
*******************************************************************************/
|
||||
public Tag()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor that takes a QRecord
|
||||
*******************************************************************************/
|
||||
public Tag(QRecord record)
|
||||
{
|
||||
populateFromQRecord(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public Tag withId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
*******************************************************************************/
|
||||
public String getName()
|
||||
{
|
||||
return (this.name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for name
|
||||
*******************************************************************************/
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for name
|
||||
*******************************************************************************/
|
||||
public Tag withName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for color
|
||||
*******************************************************************************/
|
||||
public String getColor()
|
||||
{
|
||||
return (this.color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for color
|
||||
*******************************************************************************/
|
||||
public void setColor(String color)
|
||||
{
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for color
|
||||
*******************************************************************************/
|
||||
public Tag withColor(String color)
|
||||
{
|
||||
this.color = color;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for createDate
|
||||
*******************************************************************************/
|
||||
public Instant getCreateDate()
|
||||
{
|
||||
return (this.createDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for createDate
|
||||
*******************************************************************************/
|
||||
public Tag withCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for modifyDate
|
||||
*******************************************************************************/
|
||||
public Instant getModifyDate()
|
||||
{
|
||||
return (this.modifyDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for modifyDate
|
||||
*******************************************************************************/
|
||||
public void setModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for modifyDate
|
||||
*******************************************************************************/
|
||||
public Tag withModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM activity priority levels.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM assignment strategies.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM assignable entity types.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM audit log actions.
|
||||
*******************************************************************************/
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingPossibleValueEnum;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Enum of CRM audit log actions tracked for entity changes.
|
||||
*******************************************************************************/
|
||||
@QMetaDataProducingPossibleValueEnum()
|
||||
public enum CrmAuditAction implements PossibleValueEnum<Integer>
|
||||
{
|
||||
CREATED(1, "Created"),
|
||||
UPDATED(2, "Updated"),
|
||||
DELETED(3, "Deleted"),
|
||||
STAGE_CHANGED(4, "Stage Changed"),
|
||||
OWNER_CHANGED(5, "Owner Changed"),
|
||||
SCORE_CHANGED(6, "Score Changed"),
|
||||
MERGED(7, "Merged"),
|
||||
CONSENT_CHANGED(8, "Consent Changed"),
|
||||
TAG_CHANGED(9, "Tag Changed"),
|
||||
ENROLLED(10, "Enrolled"),
|
||||
UNENROLLED(11, "Unenrolled"),
|
||||
IMPORTED(12, "Imported"),
|
||||
ANONYMIZED(13, "Anonymized"),
|
||||
FORM_CONVERTED(14, "Form Converted");
|
||||
|
||||
private final Integer id;
|
||||
private final String label;
|
||||
|
||||
public static final String NAME = "CrmAuditAction";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
*******************************************************************************/
|
||||
CrmAuditAction(Integer id, String label)
|
||||
{
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get instance by id
|
||||
*******************************************************************************/
|
||||
public static CrmAuditAction getById(Integer id)
|
||||
{
|
||||
if(id == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
for(CrmAuditAction value : CrmAuditAction.values())
|
||||
{
|
||||
if(Objects.equals(value.id, id))
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for label
|
||||
*******************************************************************************/
|
||||
public String getLabel()
|
||||
{
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Integer getPossibleValueId()
|
||||
{
|
||||
return (getId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getPossibleValueLabel()
|
||||
{
|
||||
return (getLabel());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM email bounce types.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM company types.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM consent statuses.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM consent types.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM deal priority levels.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM direction (inbound/outbound).
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM email statuses.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM sequence enrollment statuses.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM entity types.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM form conversion statuses.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM form types.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM goal periods.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM legal basis types (GDPR).
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM list member statuses.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM activity participant roles.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM scorable entity types.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM scoring rule operators.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM sequence step types.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM sync statuses.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible value source enum for CRM deal win/loss types.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.model.enums;
|
||||
|
||||
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
/*******************************************************************************
|
||||
** Bulk action process to add a tag to selected contact records. Inserts a
|
||||
** junction row in crmContactTag if one does not already exist.
|
||||
*******************************************************************************/
|
||||
package com.kingsrook.qbits.crm.core.processes;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.crm.core.model.Contact;
|
||||
import com.kingsrook.qbits.crm.core.model.ContactTag;
|
||||
import com.kingsrook.qbits.crm.core.model.Tag;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** MetaDataProducer + BackendStep for bulk-adding a tag to contacts.
|
||||
** For each selected record, inserts a crmContactTag junction row if one
|
||||
** does not already exist for that contactId + tagId combination.
|
||||
*******************************************************************************/
|
||||
public class BulkAddTagProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "bulkAddTag";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Produce the process metadata.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QProcessMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
return new QProcessMetaData()
|
||||
.withName(NAME)
|
||||
.withLabel("Bulk Add Tag")
|
||||
.withTableName(Contact.TABLE_NAME)
|
||||
.withIcon(new QIcon().withName("label"))
|
||||
.withStepList(List.of(
|
||||
new QBackendStepMetaData()
|
||||
.withName("execute")
|
||||
.withCode(new QCodeReference(BulkAddTagProcess.class))
|
||||
.withInputData(new QFunctionInputMetaData().withFieldList(List.of(
|
||||
new QFieldMetaData("tagId", QFieldType.INTEGER).withIsRequired(true).withPossibleValueSourceName(Tag.TABLE_NAME),
|
||||
new QFieldMetaData("recordIds", QFieldType.STRING).withIsRequired(true).withLabel("Record IDs (comma-separated)")
|
||||
)))
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Execute: for each record id, insert a junction row if not exists.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
|
||||
{
|
||||
Integer tagId = input.getValueInteger("tagId");
|
||||
String recordIds = input.getValueString("recordIds");
|
||||
|
||||
Integer addedCount = 0;
|
||||
Integer skippedCount = 0;
|
||||
|
||||
for(String idStr : recordIds.split(","))
|
||||
{
|
||||
Integer contactId = Integer.parseInt(idStr.trim());
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// check if the junction row already exists //
|
||||
/////////////////////////////////////////////////////////////
|
||||
QueryOutput existingTags = new QueryAction().execute(
|
||||
new QueryInput(ContactTag.TABLE_NAME)
|
||||
.withFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("contactId", QCriteriaOperator.EQUALS, contactId))
|
||||
.withCriteria(new QFilterCriteria("tagId", QCriteriaOperator.EQUALS, tagId))));
|
||||
|
||||
if(existingTags.getRecords().isEmpty())
|
||||
{
|
||||
new InsertAction().execute(
|
||||
new InsertInput(ContactTag.TABLE_NAME).withRecordEntity(
|
||||
new ContactTag()
|
||||
.withContactId(contactId)
|
||||
.withTagId(tagId)));
|
||||
addedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
skippedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
output.addValue("addedCount", addedCount);
|
||||
output.addValue("skippedCount", skippedCount);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,29 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Bulk action process to assign a new owner to selected records. Works on
|
||||
** any table that has an ownerUserId field.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.processes;
|
||||
|
||||
|
||||
@@ -32,7 +54,7 @@ import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwith
|
||||
*******************************************************************************/
|
||||
public class BulkAssignOwnerProcess implements MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "bulkAssignOwner";
|
||||
public static final String NAME = "crm_bulk_assign_owner";
|
||||
|
||||
|
||||
|
||||
@@ -71,8 +93,6 @@ public class BulkAssignOwnerProcess implements MetaDataProducerInterface<QProces
|
||||
.withSingularPastMessage("was assigned a new owner")
|
||||
.withPluralPastMessage("were assigned a new owner");
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Set ownerUserId on each record from the newOwnerUserId input.
|
||||
*******************************************************************************/
|
||||
@@ -89,8 +109,6 @@ public class BulkAssignOwnerProcess implements MetaDataProducerInterface<QProces
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Provide process summary.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
/*******************************************************************************
|
||||
** Bulk action process to remove a tag from selected contact records. Deletes
|
||||
** the junction row in crmContactTag.
|
||||
*******************************************************************************/
|
||||
package com.kingsrook.qbits.crm.core.processes;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.crm.core.model.Contact;
|
||||
import com.kingsrook.qbits.crm.core.model.ContactTag;
|
||||
import com.kingsrook.qbits.crm.core.model.Tag;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** MetaDataProducer + BackendStep for bulk-removing a tag from contacts.
|
||||
** For each selected record, deletes the crmContactTag junction row if it
|
||||
** exists for that contactId + tagId combination.
|
||||
*******************************************************************************/
|
||||
public class BulkRemoveTagProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "bulkRemoveTag";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Produce the process metadata.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QProcessMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
return new QProcessMetaData()
|
||||
.withName(NAME)
|
||||
.withLabel("Bulk Remove Tag")
|
||||
.withTableName(Contact.TABLE_NAME)
|
||||
.withIcon(new QIcon().withName("label_off"))
|
||||
.withStepList(List.of(
|
||||
new QBackendStepMetaData()
|
||||
.withName("execute")
|
||||
.withCode(new QCodeReference(BulkRemoveTagProcess.class))
|
||||
.withInputData(new QFunctionInputMetaData().withFieldList(List.of(
|
||||
new QFieldMetaData("tagId", QFieldType.INTEGER).withIsRequired(true).withPossibleValueSourceName(Tag.TABLE_NAME),
|
||||
new QFieldMetaData("recordIds", QFieldType.STRING).withIsRequired(true).withLabel("Record IDs (comma-separated)")
|
||||
)))
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Execute: for each record id, delete the junction row if it exists.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
|
||||
{
|
||||
Integer tagId = input.getValueInteger("tagId");
|
||||
String recordIds = input.getValueString("recordIds");
|
||||
|
||||
Integer removedCount = 0;
|
||||
Integer skippedCount = 0;
|
||||
|
||||
for(String idStr : recordIds.split(","))
|
||||
{
|
||||
Integer contactId = Integer.parseInt(idStr.trim());
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// find the junction row //
|
||||
/////////////////////////////////////////////////////////////
|
||||
QueryOutput existingTags = new QueryAction().execute(
|
||||
new QueryInput(ContactTag.TABLE_NAME)
|
||||
.withFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("contactId", QCriteriaOperator.EQUALS, contactId))
|
||||
.withCriteria(new QFilterCriteria("tagId", QCriteriaOperator.EQUALS, tagId))));
|
||||
|
||||
if(!existingTags.getRecords().isEmpty())
|
||||
{
|
||||
QRecord tagRecord = existingTags.getRecords().get(0);
|
||||
new DeleteAction().execute(
|
||||
new DeleteInput(ContactTag.TABLE_NAME)
|
||||
.withPrimaryKeys(List.of(tagRecord.getValueInteger("id"))));
|
||||
removedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
skippedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
output.addValue("removedCount", removedCount);
|
||||
output.addValue("skippedCount", skippedCount);
|
||||
}
|
||||
|
||||
}
|
||||
+24
-2
@@ -1,8 +1,30 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to convert a form submission into a CRM contact. Handles duplicate
|
||||
** detection by email, creates the contact if needed, updates the form
|
||||
** submission record, and logs a Note activity on the contact.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.processes;
|
||||
|
||||
|
||||
@@ -21,7 +43,6 @@ import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
@@ -33,6 +54,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
@@ -51,7 +73,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
*******************************************************************************/
|
||||
public class ConvertFormToContactProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "convertFormToContact";
|
||||
public static final String NAME = "crm_convert_form_to_contact";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to detect duplicate contacts (by email) or companies (by domain).
|
||||
** Returns groups where count > 1.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.processes;
|
||||
|
||||
|
||||
@@ -36,7 +58,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
*******************************************************************************/
|
||||
public class DetectDuplicatesProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "detectDuplicates";
|
||||
public static final String NAME = "crm_detect_duplicates";
|
||||
|
||||
|
||||
|
||||
|
||||
+27
-98
@@ -1,35 +1,49 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** GDPR right-to-erasure process. Anonymizes PII on a contact, scrubs audit
|
||||
** log entries, deletes attachments, unenrolls from sequences, creates a
|
||||
** consent-withdrawn record, and logs the anonymization event.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.processes;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.crm.audit.model.AuditLog;
|
||||
import com.kingsrook.qbits.crm.core.model.Attachment;
|
||||
import com.kingsrook.qbits.crm.core.model.ConsentRecord;
|
||||
import com.kingsrook.qbits.crm.core.model.Contact;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmAuditAction;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmConsentStatus;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmConsentType;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEnrollmentStatus;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEntityType;
|
||||
import com.kingsrook.qbits.crm.CrmSessionUtils;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
@@ -56,7 +70,7 @@ public class GdprAnonymizeContactProcess implements BackendStep, MetaDataProduce
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(GdprAnonymizeContactProcess.class);
|
||||
|
||||
public static final String NAME = "gdprAnonymizeContact";
|
||||
public static final String NAME = "crm_gdpr_anonymize_contact";
|
||||
|
||||
private static final String ANONYMIZED = "[ANONYMIZED]";
|
||||
|
||||
@@ -92,7 +106,6 @@ public class GdprAnonymizeContactProcess implements BackendStep, MetaDataProduce
|
||||
public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
|
||||
{
|
||||
Integer contactId = input.getValueInteger("contactId");
|
||||
String sessionUserId = CrmSessionUtils.getCurrentUserId();
|
||||
|
||||
///////////////////////////////////////////
|
||||
// Step 1: load and validate contact //
|
||||
@@ -130,22 +143,12 @@ public class GdprAnonymizeContactProcess implements BackendStep, MetaDataProduce
|
||||
new UpdateInput(Contact.TABLE_NAME).withRecord(updateRecord));
|
||||
|
||||
///////////////////////////////////////////
|
||||
// Step 3: scrub audit log entries //
|
||||
///////////////////////////////////////////
|
||||
scrubAuditLogEntries(contactId);
|
||||
|
||||
///////////////////////////////////////////
|
||||
// Step 4: delete attachments //
|
||||
///////////////////////////////////////////
|
||||
deleteAttachments(contactId);
|
||||
|
||||
///////////////////////////////////////////
|
||||
// Step 5: unenroll from sequences //
|
||||
// Step 3: unenroll from sequences //
|
||||
///////////////////////////////////////////
|
||||
unenrollFromSequences(contactId);
|
||||
|
||||
///////////////////////////////////////////
|
||||
// Step 6: create consent record //
|
||||
// Step 4: create consent record //
|
||||
///////////////////////////////////////////
|
||||
new InsertAction().execute(
|
||||
new InsertInput(ConsentRecord.TABLE_NAME).withRecordEntity(
|
||||
@@ -156,86 +159,12 @@ public class GdprAnonymizeContactProcess implements BackendStep, MetaDataProduce
|
||||
.withSource("GDPR Anonymization Process")
|
||||
.withConsentDate(Instant.now())));
|
||||
|
||||
///////////////////////////////////////////
|
||||
// Step 7: log ANONYMIZED audit entry //
|
||||
///////////////////////////////////////////
|
||||
new InsertAction().execute(
|
||||
new InsertInput(AuditLog.TABLE_NAME).withRecordEntity(
|
||||
new AuditLog()
|
||||
.withEntityType(CrmEntityType.CONTACT.getPossibleValueId())
|
||||
.withEntityId(contactId)
|
||||
.withAction(CrmAuditAction.ANONYMIZED.getPossibleValueId())
|
||||
.withUserId(sessionUserId)
|
||||
.withMessage("Contact anonymized per GDPR right-to-erasure request")));
|
||||
|
||||
output.addValue("contactId", contactId);
|
||||
output.addValue("anonymized", true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** Scrub audit log entries for this contact. Sets a GDPR bypass flag on
|
||||
** the session so the immutability customizers allow the update.
|
||||
***************************************************************************/
|
||||
private void scrubAuditLogEntries(Integer contactId) throws QException
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// set GDPR bypass flag on session //
|
||||
///////////////////////////////////////////
|
||||
QContext.getQSession().setValue("gdprBypass", "true");
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
// query audit entries for this contact //
|
||||
/////////////////////////////////////////////
|
||||
QueryOutput auditEntries = new QueryAction().execute(
|
||||
new QueryInput(AuditLog.TABLE_NAME)
|
||||
.withFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("entityType", QCriteriaOperator.EQUALS, CrmEntityType.CONTACT.getPossibleValueId()))
|
||||
.withCriteria(new QFilterCriteria("entityId", QCriteriaOperator.EQUALS, contactId))));
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// anonymize each entry //
|
||||
/////////////////////////////////////////////
|
||||
for(QRecord record : auditEntries.getRecords())
|
||||
{
|
||||
QRecord update = new QRecord();
|
||||
update.setValue("id", record.getValueInteger("id"));
|
||||
update.setValue("oldValue", ANONYMIZED);
|
||||
update.setValue("newValue", ANONYMIZED);
|
||||
update.setValue("message", ANONYMIZED);
|
||||
new UpdateAction().execute(new UpdateInput(AuditLog.TABLE_NAME).withRecords(List.of(update)));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.getQSession().setValue("gdprBypass", "false");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** Delete attachments linked to this contact.
|
||||
***************************************************************************/
|
||||
private void deleteAttachments(Integer contactId) throws QException
|
||||
{
|
||||
QueryOutput attachments = new QueryAction().execute(
|
||||
new QueryInput(Attachment.TABLE_NAME)
|
||||
.withFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("entityType", QCriteriaOperator.EQUALS, CrmEntityType.CONTACT.getPossibleValueId()))
|
||||
.withCriteria(new QFilterCriteria("entityId", QCriteriaOperator.EQUALS, contactId))));
|
||||
|
||||
for(QRecord record : attachments.getRecords())
|
||||
{
|
||||
new DeleteAction().execute(
|
||||
new DeleteInput(Attachment.TABLE_NAME).withPrimaryKeys(List.of(record.getValueInteger("id"))));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** Unenroll from any active sequence enrollments for this contact.
|
||||
***************************************************************************/
|
||||
@@ -244,7 +173,7 @@ public class GdprAnonymizeContactProcess implements BackendStep, MetaDataProduce
|
||||
try
|
||||
{
|
||||
QueryOutput enrollments = new QueryAction().execute(
|
||||
new QueryInput("crmSequenceEnrollment")
|
||||
new QueryInput("crm_sequence_enrollment")
|
||||
.withFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("contactId", QCriteriaOperator.EQUALS, contactId))
|
||||
.withCriteria(new QFilterCriteria("status", QCriteriaOperator.EQUALS, CrmEnrollmentStatus.ACTIVE.getPossibleValueId()))));
|
||||
@@ -256,7 +185,7 @@ public class GdprAnonymizeContactProcess implements BackendStep, MetaDataProduce
|
||||
.withValue("status", CrmEnrollmentStatus.UNENROLLED.getPossibleValueId());
|
||||
|
||||
new UpdateAction().execute(
|
||||
new UpdateInput("crmSequenceEnrollment").withRecord(updateRecord));
|
||||
new UpdateInput("crm_sequence_enrollment").withRecord(updateRecord));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Simplified import process for contacts. Takes a list of QRecords,
|
||||
** validates required fields, checks for duplicates by email, inserts new
|
||||
** contacts, and logs audit entries.
|
||||
** contacts, with core table auditing.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.processes;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qbits.crm.audit.model.AuditLog;
|
||||
import com.kingsrook.qbits.crm.core.model.Contact;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmAuditAction;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEntityType;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
@@ -22,10 +40,6 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@@ -43,7 +57,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
*******************************************************************************/
|
||||
public class ImportContactsProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "importContacts";
|
||||
public static final String NAME = "crm_import_contacts";
|
||||
|
||||
|
||||
|
||||
@@ -129,24 +143,11 @@ public class ImportContactsProcess implements BackendStep, MetaDataProducerInter
|
||||
.withValue("phone", inputRecord.getValueString("phone"))
|
||||
.withValue("ownerUserId", sessionUserId);
|
||||
|
||||
InsertOutput insertOutput = new InsertAction().execute(
|
||||
new InsertAction().execute(
|
||||
new InsertInput(Contact.TABLE_NAME).withRecord(newContact));
|
||||
Integer newContactId = insertOutput.getRecords().get(0).getValueInteger("id");
|
||||
|
||||
existingEmails.add(email.toLowerCase());
|
||||
|
||||
///////////////////////////////////////////
|
||||
// log IMPORTED audit entry //
|
||||
///////////////////////////////////////////
|
||||
new InsertAction().execute(
|
||||
new InsertInput(AuditLog.TABLE_NAME).withRecordEntity(
|
||||
new AuditLog()
|
||||
.withEntityType(CrmEntityType.CONTACT.getPossibleValueId())
|
||||
.withEntityId(newContactId)
|
||||
.withAction(CrmAuditAction.IMPORTED.getPossibleValueId())
|
||||
.withUserId(sessionUserId)
|
||||
.withMessage("Contact imported")));
|
||||
|
||||
importedCount++;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +1,48 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to merge two companies. Re-links contacts, deals, activities, and
|
||||
** tags from the secondary to the primary, then deletes the secondary company
|
||||
** and logs audit entries.
|
||||
** related records from the secondary to the primary, then deletes the secondary company
|
||||
** with core table auditing.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.processes;
|
||||
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qbits.crm.activities.model.Activity;
|
||||
import com.kingsrook.qbits.crm.audit.model.AuditLog;
|
||||
import com.kingsrook.qbits.crm.core.model.Company;
|
||||
import com.kingsrook.qbits.crm.core.model.CompanyTag;
|
||||
import com.kingsrook.qbits.crm.core.model.Contact;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmAuditAction;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEntityType;
|
||||
import com.kingsrook.qbits.crm.deals.model.Deal;
|
||||
import com.kingsrook.qbits.crm.CrmSessionUtils;
|
||||
import com.kingsrook.qbits.crm.activities.model.Activity;
|
||||
import com.kingsrook.qbits.crm.core.model.Company;
|
||||
import com.kingsrook.qbits.crm.core.model.Contact;
|
||||
import com.kingsrook.qbits.crm.deals.model.Deal;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@@ -52,7 +66,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
*******************************************************************************/
|
||||
public class MergeCompaniesProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "mergeCompanies";
|
||||
public static final String NAME = "crm_merge_companies";
|
||||
|
||||
|
||||
|
||||
@@ -117,10 +131,6 @@ public class MergeCompaniesProcess implements BackendStep, MetaDataProducerInter
|
||||
///////////////////////////////////////////////
|
||||
relinkRecords(Activity.TABLE_NAME, "companyId", secondaryCompanyId, primaryCompanyId);
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Re-link CompanyTag rows (skip duplicates) //
|
||||
///////////////////////////////////////////////
|
||||
relinkCompanyTags(primaryCompanyId, secondaryCompanyId);
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Delete the secondary company //
|
||||
@@ -128,27 +138,6 @@ public class MergeCompaniesProcess implements BackendStep, MetaDataProducerInter
|
||||
new DeleteAction().execute(
|
||||
new DeleteInput(Company.TABLE_NAME).withPrimaryKeys(List.of(secondaryCompanyId)));
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Log audit entries //
|
||||
///////////////////////////////////////////////
|
||||
new InsertAction().execute(
|
||||
new InsertInput(AuditLog.TABLE_NAME).withRecordEntity(
|
||||
new AuditLog()
|
||||
.withEntityType(CrmEntityType.COMPANY.getPossibleValueId())
|
||||
.withEntityId(primaryCompanyId)
|
||||
.withAction(CrmAuditAction.MERGED.getPossibleValueId())
|
||||
.withUserId(sessionUserId)
|
||||
.withMessage("Merged company " + secondaryCompanyId + " into this company")));
|
||||
|
||||
new InsertAction().execute(
|
||||
new InsertInput(AuditLog.TABLE_NAME).withRecordEntity(
|
||||
new AuditLog()
|
||||
.withEntityType(CrmEntityType.COMPANY.getPossibleValueId())
|
||||
.withEntityId(secondaryCompanyId)
|
||||
.withAction(CrmAuditAction.DELETED.getPossibleValueId())
|
||||
.withUserId(sessionUserId)
|
||||
.withMessage("Company deleted during merge into company " + primaryCompanyId)));
|
||||
|
||||
output.addValue("primaryCompanyId", primaryCompanyId);
|
||||
}
|
||||
|
||||
@@ -177,44 +166,4 @@ public class MergeCompaniesProcess implements BackendStep, MetaDataProducerInter
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** Re-link CompanyTag rows: skip duplicates (same tagId already on primary).
|
||||
***************************************************************************/
|
||||
private void relinkCompanyTags(Integer primaryCompanyId, Integer secondaryCompanyId) throws QException
|
||||
{
|
||||
QueryOutput primaryTags = new QueryAction().execute(
|
||||
new QueryInput(CompanyTag.TABLE_NAME)
|
||||
.withFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("companyId", QCriteriaOperator.EQUALS, primaryCompanyId))));
|
||||
|
||||
Set<Integer> primaryTagIds = new HashSet<>();
|
||||
for(QRecord record : primaryTags.getRecords())
|
||||
{
|
||||
primaryTagIds.add(record.getValueInteger("tagId"));
|
||||
}
|
||||
|
||||
QueryOutput secondaryTags = new QueryAction().execute(
|
||||
new QueryInput(CompanyTag.TABLE_NAME)
|
||||
.withFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("companyId", QCriteriaOperator.EQUALS, secondaryCompanyId))));
|
||||
|
||||
for(QRecord record : secondaryTags.getRecords())
|
||||
{
|
||||
Integer tagId = record.getValueInteger("tagId");
|
||||
if(primaryTagIds.contains(tagId))
|
||||
{
|
||||
new DeleteAction().execute(
|
||||
new DeleteInput(CompanyTag.TABLE_NAME).withPrimaryKeys(List.of(record.getValueInteger("id"))));
|
||||
}
|
||||
else
|
||||
{
|
||||
QRecord updateRecord = new QRecord()
|
||||
.withValue("id", record.getValueInteger("id"))
|
||||
.withValue("companyId", primaryCompanyId);
|
||||
new UpdateAction().execute(
|
||||
new UpdateInput(CompanyTag.TABLE_NAME).withRecord(updateRecord));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +1,50 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to merge two contacts. Re-links activities, deal contacts, tags,
|
||||
** Process to merge two contacts. Re-links activities, deal contacts,
|
||||
** and email list memberships from the secondary to the primary, then deletes
|
||||
** the secondary contact and logs audit entries.
|
||||
** the secondary contact with core table auditing.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.processes;
|
||||
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qbits.crm.activities.model.Activity;
|
||||
import com.kingsrook.qbits.crm.audit.model.AuditLog;
|
||||
import com.kingsrook.qbits.crm.core.model.Contact;
|
||||
import com.kingsrook.qbits.crm.core.model.ContactTag;
|
||||
import com.kingsrook.qbits.crm.core.model.EmailListMember;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmAuditAction;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEntityType;
|
||||
import com.kingsrook.qbits.crm.deals.model.DealContact;
|
||||
import com.kingsrook.qbits.crm.CrmSessionUtils;
|
||||
import com.kingsrook.qbits.crm.activities.model.Activity;
|
||||
import com.kingsrook.qbits.crm.core.model.Contact;
|
||||
import com.kingsrook.qbits.crm.core.model.EmailListMember;
|
||||
import com.kingsrook.qbits.crm.deals.model.DealContact;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@@ -52,7 +68,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
*******************************************************************************/
|
||||
public class MergeContactsProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "mergeContacts";
|
||||
public static final String NAME = "crm_merge_contacts";
|
||||
|
||||
|
||||
|
||||
@@ -115,10 +131,6 @@ public class MergeContactsProcess implements BackendStep, MetaDataProducerInterf
|
||||
///////////////////////////////////////////////
|
||||
relinkDealContacts(primaryContactId, secondaryContactId);
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Re-link ContactTag rows //
|
||||
///////////////////////////////////////////////
|
||||
relinkJunctionRecords(ContactTag.TABLE_NAME, "contactId", "tagId", primaryContactId, secondaryContactId);
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Re-link EmailListMember rows //
|
||||
@@ -131,27 +143,6 @@ public class MergeContactsProcess implements BackendStep, MetaDataProducerInterf
|
||||
new DeleteAction().execute(
|
||||
new DeleteInput(Contact.TABLE_NAME).withPrimaryKeys(List.of(secondaryContactId)));
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Log audit entries //
|
||||
///////////////////////////////////////////////
|
||||
new InsertAction().execute(
|
||||
new InsertInput(AuditLog.TABLE_NAME).withRecordEntity(
|
||||
new AuditLog()
|
||||
.withEntityType(CrmEntityType.CONTACT.getPossibleValueId())
|
||||
.withEntityId(primaryContactId)
|
||||
.withAction(CrmAuditAction.MERGED.getPossibleValueId())
|
||||
.withUserId(sessionUserId)
|
||||
.withMessage("Merged contact " + secondaryContactId + " into this contact")));
|
||||
|
||||
new InsertAction().execute(
|
||||
new InsertInput(AuditLog.TABLE_NAME).withRecordEntity(
|
||||
new AuditLog()
|
||||
.withEntityType(CrmEntityType.CONTACT.getPossibleValueId())
|
||||
.withEntityId(secondaryContactId)
|
||||
.withAction(CrmAuditAction.DELETED.getPossibleValueId())
|
||||
.withUserId(sessionUserId)
|
||||
.withMessage("Contact deleted during merge into contact " + primaryContactId)));
|
||||
|
||||
output.addValue("primaryContactId", primaryContactId);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to subscribe an email address to an email list. Handles
|
||||
** resubscription after unsubscribe and links to an existing contact.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.processes;
|
||||
|
||||
|
||||
@@ -15,7 +37,6 @@ import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
@@ -26,6 +47,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
@@ -44,7 +66,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
*******************************************************************************/
|
||||
public class SubscribeToListProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "subscribeToList";
|
||||
public static final String NAME = "crm_subscribe_to_list";
|
||||
|
||||
|
||||
|
||||
|
||||
+24
-2
@@ -1,7 +1,29 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to unsubscribe an email address from an email list. Sets the member
|
||||
** status to UNSUBSCRIBED and records the unsubscribedDate.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.core.processes;
|
||||
|
||||
|
||||
@@ -13,7 +35,6 @@ import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
@@ -23,6 +44,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
@@ -40,7 +62,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
*******************************************************************************/
|
||||
public class UnsubscribeFromListProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "unsubscribeFromList";
|
||||
public static final String NAME = "crm_unsubscribe_from_list";
|
||||
|
||||
|
||||
|
||||
|
||||
+24
-2
@@ -1,8 +1,30 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** POST_INSERT table customizer that initializes newly-created deals with
|
||||
** computed fields: stageEnteredDate, weightedAmount, amountInBaseCurrency,
|
||||
** and the initial DealStageHistory row.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.customizers;
|
||||
|
||||
|
||||
@@ -10,11 +32,11 @@ import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.crm.CrmSessionUtils;
|
||||
import com.kingsrook.qbits.crm.deals.model.Deal;
|
||||
import com.kingsrook.qbits.crm.sync.CrmCurrencyUtils;
|
||||
import com.kingsrook.qbits.crm.deals.model.DealStageHistory;
|
||||
import com.kingsrook.qbits.crm.deals.model.PipelineStage;
|
||||
import com.kingsrook.qbits.crm.CrmSessionUtils;
|
||||
import com.kingsrook.qbits.crm.sync.CrmCurrencyUtils;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostInsertCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
|
||||
+22
@@ -1,8 +1,30 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** POST_INSERT, POST_UPDATE, and POST_DELETE table customizer for
|
||||
** DealProduct that recalculates line-item totalAmount and rolls up
|
||||
** the sum to the parent Deal's amount field.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.customizers;
|
||||
|
||||
|
||||
|
||||
+24
-2
@@ -1,3 +1,24 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** PRE_UPDATE table customizer that handles deal pipeline stage transitions.
|
||||
**
|
||||
@@ -9,6 +30,7 @@
|
||||
** - clears actualCloseDate and winLossReasonId on reopen
|
||||
** - inserts a DealStageHistory row
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.customizers;
|
||||
|
||||
|
||||
@@ -21,11 +43,11 @@ import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qbits.crm.CrmSessionUtils;
|
||||
import com.kingsrook.qbits.crm.deals.model.Deal;
|
||||
import com.kingsrook.qbits.crm.sync.CrmCurrencyUtils;
|
||||
import com.kingsrook.qbits.crm.deals.model.DealStageHistory;
|
||||
import com.kingsrook.qbits.crm.deals.model.PipelineStage;
|
||||
import com.kingsrook.qbits.crm.CrmSessionUtils;
|
||||
import com.kingsrook.qbits.crm.sync.CrmCurrencyUtils;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreUpdateCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for ContactRole table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.model;
|
||||
|
||||
|
||||
@@ -30,7 +52,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
)
|
||||
public class ContactRole extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmContactRole";
|
||||
public static final String TABLE_NAME = "crm_contact_role";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Deal table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.model;
|
||||
|
||||
|
||||
@@ -41,7 +63,7 @@ import com.kingsrook.qqq.backend.core.utils.collections.MutableList;
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Deal table -- the primary revenue-tracking record in the
|
||||
** CRM. Deals move through pipeline stages and are linked to contacts, companies,
|
||||
** products, activities, and tags.
|
||||
** products and activities.
|
||||
*******************************************************************************/
|
||||
@QMetaDataProducingEntity(
|
||||
produceTableMetaData = true,
|
||||
@@ -58,11 +80,6 @@ import com.kingsrook.qqq.backend.core.utils.collections.MutableList;
|
||||
joinFieldName = "dealId",
|
||||
childJoin = @ChildJoin(enabled = true),
|
||||
childRecordListWidget = @ChildRecordListWidget(label = "Products", enabled = true, maxRows = 50)),
|
||||
@ChildTable(
|
||||
childTableEntityClass = DealTag.class,
|
||||
joinFieldName = "dealId",
|
||||
childJoin = @ChildJoin(enabled = true),
|
||||
childRecordListWidget = @ChildRecordListWidget(label = "Tags", enabled = true, maxRows = 50)),
|
||||
@ChildTable(
|
||||
childTableEntityClass = DealStageHistory.class,
|
||||
joinFieldName = "dealId",
|
||||
@@ -77,7 +94,7 @@ import com.kingsrook.qqq.backend.core.utils.collections.MutableList;
|
||||
)
|
||||
public class Deal extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmDeal";
|
||||
public static final String TABLE_NAME = "crm_deal";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for DealContact table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.model;
|
||||
|
||||
|
||||
@@ -29,7 +51,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
)
|
||||
public class DealContact extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmDealContact";
|
||||
public static final String TABLE_NAME = "crm_deal_contact";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for the crm_deal_product table.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.model;
|
||||
|
||||
|
||||
@@ -32,7 +54,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
)
|
||||
public class DealProduct extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmDealProduct";
|
||||
public static final String TABLE_NAME = "crm_deal_product";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for DealStageHistory table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.model;
|
||||
|
||||
|
||||
@@ -27,7 +49,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
)
|
||||
public class DealStageHistory extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmDealStageHistory";
|
||||
public static final String TABLE_NAME = "crm_deal_stage_history";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for DealTag table
|
||||
*******************************************************************************/
|
||||
package com.kingsrook.qbits.crm.deals.model;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qbits.crm.core.model.Tag;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for DealTag junction table -- many-to-many link between
|
||||
** deals and tags.
|
||||
*******************************************************************************/
|
||||
@QMetaDataProducingEntity(
|
||||
produceTableMetaData = true,
|
||||
tableMetaDataCustomizer = DealTag.TableMetaDataCustomizer.class
|
||||
)
|
||||
public class DealTag extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmDealTag";
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static class TableMetaDataCustomizer implements MetaDataCustomizerInterface<QTableMetaData>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QTableMetaData customizeMetaData(QInstance qInstance, QTableMetaData table) throws QException
|
||||
{
|
||||
table
|
||||
.withIcon(new QIcon().withName("label"))
|
||||
.withRecordLabelFormat("Deal %s - Tag %s")
|
||||
.withRecordLabelFields("dealId", "tagId")
|
||||
.withUniqueKey(new UniqueKey("dealId", "tagId"))
|
||||
.withSection(SectionFactory.defaultT1("id", "dealId", "tagId"))
|
||||
.withSection(SectionFactory.defaultT3("createDate"));
|
||||
|
||||
return (table);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@QField(isEditable = false, isPrimaryKey = true)
|
||||
private Integer id;
|
||||
|
||||
@QField(isRequired = true, possibleValueSourceName = Deal.TABLE_NAME)
|
||||
private Integer dealId;
|
||||
|
||||
@QField(isRequired = true, possibleValueSourceName = Tag.TABLE_NAME)
|
||||
private Integer tagId;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant createDate;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor
|
||||
*******************************************************************************/
|
||||
public DealTag()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor that takes a QRecord
|
||||
*******************************************************************************/
|
||||
public DealTag(QRecord record)
|
||||
{
|
||||
populateFromQRecord(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public DealTag withId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for dealId
|
||||
*******************************************************************************/
|
||||
public Integer getDealId()
|
||||
{
|
||||
return (this.dealId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for dealId
|
||||
*******************************************************************************/
|
||||
public void setDealId(Integer dealId)
|
||||
{
|
||||
this.dealId = dealId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for dealId
|
||||
*******************************************************************************/
|
||||
public DealTag withDealId(Integer dealId)
|
||||
{
|
||||
this.dealId = dealId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tagId
|
||||
*******************************************************************************/
|
||||
public Integer getTagId()
|
||||
{
|
||||
return (this.tagId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tagId
|
||||
*******************************************************************************/
|
||||
public void setTagId(Integer tagId)
|
||||
{
|
||||
this.tagId = tagId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tagId
|
||||
*******************************************************************************/
|
||||
public DealTag withTagId(Integer tagId)
|
||||
{
|
||||
this.tagId = tagId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for createDate
|
||||
*******************************************************************************/
|
||||
public Instant getCreateDate()
|
||||
{
|
||||
return (this.createDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for createDate
|
||||
*******************************************************************************/
|
||||
public DealTag withCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,32 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for Pipeline table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.model;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@@ -41,7 +62,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
)
|
||||
public class Pipeline extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmPipeline";
|
||||
public static final String TABLE_NAME = "crm_pipeline";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for PipelineStage table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.model;
|
||||
|
||||
|
||||
@@ -41,7 +63,7 @@ import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage
|
||||
)
|
||||
public class PipelineStage extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmPipelineStage";
|
||||
public static final String TABLE_NAME = "crm_pipeline_stage";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for the crm_product table.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.model;
|
||||
|
||||
|
||||
@@ -44,7 +66,7 @@ import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage
|
||||
)
|
||||
public class Product extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmProduct";
|
||||
public static final String TABLE_NAME = "crm_product";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for WinLossReason table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.model;
|
||||
|
||||
|
||||
@@ -31,7 +53,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
)
|
||||
public class WinLossReason extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmWinLossReason";
|
||||
public static final String TABLE_NAME = "crm_win_loss_reason";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to clone a deal including its contacts, products, and tags.
|
||||
** Process to clone a deal including its contacts and products.
|
||||
** The cloned deal gets a "(Copy)" suffix, resets to the first pipeline stage,
|
||||
** and clears close-related fields.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.processes;
|
||||
|
||||
|
||||
@@ -11,7 +33,6 @@ import java.util.List;
|
||||
import com.kingsrook.qbits.crm.deals.model.Deal;
|
||||
import com.kingsrook.qbits.crm.deals.model.DealContact;
|
||||
import com.kingsrook.qbits.crm.deals.model.DealProduct;
|
||||
import com.kingsrook.qbits.crm.deals.model.DealTag;
|
||||
import com.kingsrook.qbits.crm.deals.model.PipelineStage;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
@@ -45,7 +66,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
*******************************************************************************/
|
||||
public class CloneDealProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "cloneDeal";
|
||||
public static final String NAME = "crm_clone_deal";
|
||||
|
||||
|
||||
|
||||
@@ -127,10 +148,6 @@ public class CloneDealProcess implements BackendStep, MetaDataProducerInterface<
|
||||
/////////////////////////////////////////////
|
||||
cloneChildRecords(DealProduct.TABLE_NAME, "dealId", dealId, newDealId);
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// Step 6: clone DealTag rows //
|
||||
/////////////////////////////////////////////
|
||||
cloneChildRecords(DealTag.TABLE_NAME, "dealId", dealId, newDealId);
|
||||
|
||||
output.addValue("dealId", newDealId);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,38 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to convert a contact (lead) into a deal opportunity. Updates the
|
||||
** contact's lifecycle stage to "Opportunity", creates a new Deal, links
|
||||
** them via DealContact junction, and logs audit entries.
|
||||
** them via DealContact junction, with core table auditing.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.processes;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.crm.audit.model.AuditLog;
|
||||
import com.kingsrook.qbits.crm.core.model.Contact;
|
||||
import com.kingsrook.qbits.crm.core.model.LifecycleStage;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmAuditAction;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEntityType;
|
||||
import com.kingsrook.qbits.crm.deals.model.Deal;
|
||||
import com.kingsrook.qbits.crm.deals.model.DealContact;
|
||||
import com.kingsrook.qbits.crm.deals.model.Pipeline;
|
||||
@@ -57,7 +76,7 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
*******************************************************************************/
|
||||
public class ConvertLeadProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "convertLead";
|
||||
public static final String NAME = "crm_convert_lead";
|
||||
|
||||
|
||||
|
||||
@@ -174,29 +193,6 @@ public class ConvertLeadProcess implements BackendStep, MetaDataProducerInterfac
|
||||
new InsertAction().execute(
|
||||
new InsertInput(DealContact.TABLE_NAME).withRecordEntity(dealContact));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Step 7: log audit entries //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
AuditLog contactAudit = new AuditLog()
|
||||
.withEntityType(CrmEntityType.CONTACT.getPossibleValueId())
|
||||
.withEntityId(contactId)
|
||||
.withAction(CrmAuditAction.STAGE_CHANGED.getPossibleValueId())
|
||||
.withUserId(userId)
|
||||
.withFieldName("lifecycleStageId")
|
||||
.withNewValue(opportunityStageId != null ? String.valueOf(opportunityStageId) : null)
|
||||
.withMessage("Lead converted to deal: " + dealName);
|
||||
|
||||
AuditLog dealAudit = new AuditLog()
|
||||
.withEntityType(CrmEntityType.DEAL.getPossibleValueId())
|
||||
.withEntityId(dealId)
|
||||
.withAction(CrmAuditAction.CREATED.getPossibleValueId())
|
||||
.withUserId(userId)
|
||||
.withMessage("Deal created from lead conversion (contact " + contactId + ")");
|
||||
|
||||
new InsertAction().execute(
|
||||
new InsertInput(AuditLog.TABLE_NAME).withRecords(List.of(
|
||||
contactAudit.toQRecord(), dealAudit.toQRecord())));
|
||||
|
||||
output.addValue("dealId", dealId);
|
||||
}
|
||||
|
||||
|
||||
+23
-1
@@ -1,8 +1,30 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to add or remove a contact from a deal via the DealContact
|
||||
** junction table. Supports isPrimary enforcement (clearing other primaries
|
||||
** when a new primary is set) and optional contactRoleId assignment.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.processes;
|
||||
|
||||
|
||||
@@ -45,7 +67,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
*******************************************************************************/
|
||||
public class ManageContactOnDealProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "manageContactOnDeal";
|
||||
public static final String NAME = "crm_manage_contact_on_deal";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to move a deal from its current pipeline stage to a target stage.
|
||||
** Validates stage membership in the pipeline, checks required fields, updates
|
||||
** weighted amount, sets close date on terminal stages, and logs stage history
|
||||
** and audit entries.
|
||||
** and core table auditing.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.processes;
|
||||
|
||||
|
||||
@@ -13,9 +35,6 @@ import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.crm.audit.model.AuditLog;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmAuditAction;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEntityType;
|
||||
import com.kingsrook.qbits.crm.deals.model.Deal;
|
||||
import com.kingsrook.qbits.crm.deals.model.DealStageHistory;
|
||||
import com.kingsrook.qbits.crm.deals.model.PipelineStage;
|
||||
@@ -52,7 +71,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
*******************************************************************************/
|
||||
public class MoveDealStageProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "moveDealStage";
|
||||
public static final String NAME = "crm_move_deal_stage";
|
||||
|
||||
|
||||
|
||||
@@ -196,22 +215,6 @@ public class MoveDealStageProcess implements BackendStep, MetaDataProducerInterf
|
||||
new InsertAction().execute(
|
||||
new InsertInput(DealStageHistory.TABLE_NAME).withRecordEntity(history));
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Step 10: log audit entry //
|
||||
//////////////////////////////////////////////////////////////
|
||||
AuditLog auditEntry = new AuditLog()
|
||||
.withEntityType(CrmEntityType.DEAL.getPossibleValueId())
|
||||
.withEntityId(dealId)
|
||||
.withAction(CrmAuditAction.STAGE_CHANGED.getPossibleValueId())
|
||||
.withUserId(sessionUserId)
|
||||
.withFieldName("pipelineStageId")
|
||||
.withOldValue(oldStageId != null ? String.valueOf(oldStageId) : null)
|
||||
.withNewValue(String.valueOf(targetStageId))
|
||||
.withMessage("Deal moved to stage: " + targetStage.getName());
|
||||
|
||||
new InsertAction().execute(
|
||||
new InsertInput(AuditLog.TABLE_NAME).withRecordEntity(auditEntry));
|
||||
|
||||
output.addValue("dealId", dealId);
|
||||
output.addValue("newStageId", targetStageId);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,37 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to reopen a closed deal by moving it to an active (non-closed)
|
||||
** pipeline stage. Clears actualCloseDate and winLossReasonId, recalculates
|
||||
** weighted amount, and logs stage history and audit entries.
|
||||
** weighted amount, and logs stage history and core table auditing.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.processes;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.crm.audit.model.AuditLog;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmAuditAction;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEntityType;
|
||||
import com.kingsrook.qbits.crm.deals.model.Deal;
|
||||
import com.kingsrook.qbits.crm.deals.model.DealStageHistory;
|
||||
import com.kingsrook.qbits.crm.deals.model.PipelineStage;
|
||||
@@ -46,7 +65,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
*******************************************************************************/
|
||||
public class ReopenDealProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "reopenDeal";
|
||||
public static final String NAME = "crm_reopen_deal";
|
||||
|
||||
|
||||
|
||||
@@ -174,22 +193,6 @@ public class ReopenDealProcess implements BackendStep, MetaDataProducerInterface
|
||||
new InsertAction().execute(
|
||||
new InsertInput(DealStageHistory.TABLE_NAME).withRecordEntity(history));
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Step 9: log audit entry //
|
||||
//////////////////////////////////////////////////////////////
|
||||
AuditLog auditEntry = new AuditLog()
|
||||
.withEntityType(CrmEntityType.DEAL.getPossibleValueId())
|
||||
.withEntityId(dealId)
|
||||
.withAction(CrmAuditAction.STAGE_CHANGED.getPossibleValueId())
|
||||
.withUserId(sessionUserId)
|
||||
.withFieldName("pipelineStageId")
|
||||
.withOldValue(oldStageId != null ? String.valueOf(oldStageId) : null)
|
||||
.withNewValue(String.valueOf(targetStageId))
|
||||
.withMessage("Deal reopened to stage: " + targetStage.getName());
|
||||
|
||||
new InsertAction().execute(
|
||||
new InsertInput(AuditLog.TABLE_NAME).withRecordEntity(auditEntry));
|
||||
|
||||
output.addValue("dealId", dealId);
|
||||
output.addValue("newStageId", targetStageId);
|
||||
}
|
||||
|
||||
+25
-20
@@ -1,26 +1,44 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process to transfer ownership of all contacts, companies, and deals from
|
||||
** one user to another, logging audit entries for each change.
|
||||
** one user to another, using core table auditing for each change.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.deals.processes;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.crm.audit.model.AuditLog;
|
||||
import com.kingsrook.qbits.crm.core.model.Company;
|
||||
import com.kingsrook.qbits.crm.core.model.Contact;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmAuditAction;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEntityType;
|
||||
import com.kingsrook.qbits.crm.deals.model.Deal;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@@ -45,7 +63,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
*******************************************************************************/
|
||||
public class TransferOwnershipProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
public static final String NAME = "transferOwnership";
|
||||
public static final String NAME = "crm_transfer_ownership";
|
||||
|
||||
|
||||
|
||||
@@ -124,20 +142,7 @@ public class TransferOwnershipProcess implements BackendStep, MetaDataProducerIn
|
||||
new UpdateAction().execute(
|
||||
new UpdateInput(tableName).withRecord(updateRecord));
|
||||
|
||||
//////////////////////////////////
|
||||
// log audit entry //
|
||||
//////////////////////////////////
|
||||
new InsertAction().execute(
|
||||
new InsertInput(AuditLog.TABLE_NAME).withRecordEntity(
|
||||
new AuditLog()
|
||||
.withEntityType(entityType.getPossibleValueId())
|
||||
.withEntityId(recordId)
|
||||
.withAction(CrmAuditAction.OWNER_CHANGED.getPossibleValueId())
|
||||
.withUserId(sessionUserId)
|
||||
.withFieldName("ownerUserId")
|
||||
.withOldValue(fromUserId)
|
||||
.withNewValue(toUserId)
|
||||
.withMessage("Ownership transferred from " + fromUserId + " to " + toUserId)));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
@@ -1,7 +1,29 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** PRE_DELETE customizer for EmailTemplate -- prevents deletion of templates
|
||||
** referenced by active email sequences.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.email.customizers;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for the crm_email_message table.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.email.model;
|
||||
|
||||
|
||||
@@ -36,7 +58,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
)
|
||||
public class EmailMessage extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmEmailMessage";
|
||||
public static final String TABLE_NAME = "crm_email_message";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for EmailSequence table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.email.model;
|
||||
|
||||
|
||||
@@ -47,7 +69,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
)
|
||||
public class EmailSequence extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmEmailSequence";
|
||||
public static final String TABLE_NAME = "crm_email_sequence";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for the crm_email_template table.
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.email.model;
|
||||
|
||||
|
||||
@@ -36,7 +58,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
)
|
||||
public class EmailTemplate extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmEmailTemplate";
|
||||
public static final String TABLE_NAME = "crm_email_template";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,33 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for SequenceEnrollment table
|
||||
*******************************************************************************/
|
||||
|
||||
package com.kingsrook.qbits.crm.email.model;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.crm.core.model.enums.CrmEnrollmentStatus;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
@@ -20,7 +43,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -33,7 +55,7 @@ import java.util.List;
|
||||
)
|
||||
public class SequenceEnrollment extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "crmSequenceEnrollment";
|
||||
public static final String TABLE_NAME = "crm_sequence_enrollment";
|
||||
|
||||
|
||||
|
||||
|
||||
Loaded 100 of 229 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user