remp / crm-application-module
CRM 应用模块
Requires
- php: ^8.1
- ext-curl: *
- ext-dom: *
- ext-filter: *
- ext-hash: *
- ext-iconv: *
- ext-json: *
- ext-pdo: *
- ext-simplexml: *
- ext-zip: *
- composer/composer: ^2.0
- contributte/forms-multiplier: ^4.0
- contributte/translation: ^2.0
- fakerphp/faker: ^1.13
- kdyby/autowired: ^3.0
- latte/latte: ^3.0
- league/csv: ^9.6
- league/event: ~2.1
- league/flysystem: ^3.0
- malkusch/lock: ^2.1
- monolog/monolog: ^1.23|^2.3|^3.5
- nette/application: ^3.2
- nette/bootstrap: ^3.1
- nette/caching: ^3.1
- nette/database: ^3.1
- nette/di: ^3.0
- nette/forms: ^3.1
- nette/http: ^3.1
- nette/mail: ^3.1
- nette/robot-loader: ^4.0
- nette/security: ^3.1 <3.2
- nette/utils: ^4.0
- odan/phinx-migrations-generator: ^6.1
- phpoffice/phpspreadsheet: ^1.2
- predis/predis: ^1.0
- robmorgan/phinx: ~0.15
- symfony/console: ^6.4
- symfony/yaml: ^6.4
- tomaj/hermes: ^4.0
- tomaj/nette-bootstrap-form: ^2.1
- tracy/tracy: ^2.8
- twig/twig: ^2.4
- vlucas/phpdotenv: ^5.3
- dev-master
- 3.4.0
- 3.3.0
- 3.2.0
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.0
- 2.11.0
- 2.10.0
- 2.9.0
- 2.8.0
- 2.7.0
- 2.6.0
- 2.5.0
- 2.4.1
- 2.4.0
- 2.3.0
- 2.2.0
- 2.1.1
- 2.1.0
- 2.0.0
- 1.2.1
- 1.2.0
- 1.1.0
- 1.0.4
- 1.0.2
- 1.0.1
- 1.0.0
- 1.0.0-beta2
- 1.0.0-beta1
- 0.39.0
- 0.38.0
- 0.37.0
- 0.36.0
- 0.35.1
- 0.35.0
- 0.34.0
- 0.33.2
- 0.33.1
- 0.33.0
- 0.32.0
- 0.31.0
- 0.30.1
- 0.30.0
- 0.29.0
- 0.28.0
- 0.27.0
- 0.26.0
- 0.25.0
- 0.24.0
- 0.23.0
- 0.22.0
- 0.21.1
- 0.21.0
- 0.20.3
- 0.20.2
- 0.20.0
- 0.18.0
- 0.17.0
- 0.16.0
- 0.15.0
- 0.14.0
- 0.13.0
- 0.12.0
- 0.11.0
- 0.10.0
- 0.9.0
- 0.8.2
- 0.8.1
- 0.8.0
- 0.7.0
- 0.6.0
- 0.5.0
- 0.4.2
- 0.4.1
- 0.4.0
- 0.3.2
- 0.3.1
- 0.3.0
- 0.2.10
- 0.2.9
- 0.2.8
- 0.2.7
- 0.2.5
- 0.2.4
- 0.2.3
- 0.2.2
- 0.2.1
- 0.2.0
- 0.1.1
This package is auto-updated.
Last update: 2024-09-20 12:56:38 UTC
README
配置
Redis
您可以配置默认的 Redis 键前缀,该前缀在通过 useRedisKeysPrefix()
方法启用前缀的情况下使用。
crm_application: redis_client_factory: prefix: foo_
您可以通过在配置中调用 useRedisKeysPrefix()
方法来使用 RedisClientTrait
在特定服务上启用前缀。
configsCache: setup: - useRedisKeysPrefix()
复制
CRM 支持使用 Redis Sentinel 进行 Redis 复制。要启用使用 sentinel,请将以下内容添加到您的 config.neon
crm_application: redis_client_factory: replication: service: my-sentinel-service sentinels: - [scheme: tcp, host: sentinel-host-a, port: 26379] - [scheme: tcp, host: sentinel-host-b, port: 26379] - [scheme: tcp, host: sentinel-host-c, port: 26379]
数据库
复制
CRM 允许您配置用于只读查询的二级数据库连接,以降低主数据库服务器的负载。请将这些块添加到您的 CRM 配置中
# replica connection parameters parameters: database: replica: adapter: @environmentConfig::get('CRM_DB_REPLICA_ADAPTER') # ENV variables are arbitrary, feel free to change them host: @environmentConfig::get('CRM_DB_REPLICA_HOST') name: @environmentConfig::get('CRM_DB_REPLICA_NAME') user: @environmentConfig::get('CRM_DB_REPLICA_USER') password: @environmentConfig::get('CRM_DB_REPLICA_PASS') port: @environmentConfig::get('CRM_DB_REPLICA_PORT') # configure replicas so the CRM is aware of them; add as many instances as you need, each under its own key database: replica: dsn: ::sprintf("%s:host=%s;dbname=%s;port=%s", %database.replica.adapter%, %database.replica.host%, %database.replica.name%, %database.replica.port%) user: %database.replica.user% password: %database.replica.password% options: lazy: yes # configure repositories to use replicaConfig (otherwise all queries would go to the primary database) decorator: Crm\ApplicationModule\Repository: setup: - setReplicaConfig(@replicaConfig) # this enables the support for reading from replicas services: replicaConfig: setup: # configure application which of the known replica DBs can be used for reads - addReplica(@database.replica.context) # configure application which DB tables can be used for replica reads # by default, every query still goes to the primary DB server; you need to explicitly allow tables, which are safe to be queried from replica - addTable(configs) - addTable(config_categories) - addTable(countries) - addTable(api_tokens) - addTable(admin_access) - addTable(admin_groups_access)
命令
application:heartbeat
如果您的 Hermes 工作器(application:hermes_worker
)在长时间不活动后丢失与 MySQL 的连接,请将 application:heartbeat
添加到您的调度器 (例如 crontab) 中,并设置小间隔 (例如 1 分钟)。
警告:根据您的安装更改 PHP 和 command.php 的路径。
# emit heartbeat event */1 * * * * /usr/bin/php /var/www/html/bin/command.php application:heartbeat
事件由 HeartbeatMysql
处理程序处理,该处理程序对 MySQL 进行 ping 操作。这个简单的流程使 Hermes 工作器保持活跃。
application:hermes_shutdown
命令 application:hermes_shutdown
可用于优雅地关闭 Hermes 工作器和所有其他集成 Hermes 的 RestartInterface
的工作器 (例如,存在于 ScenariosModule 中的场景工作器)。这可以在 CRM 更新后需要重新加载所有工作器到新版本时使用。
警告:根据您的安装更改 PHP 和 command.php 的路径。
/usr/bin/php /var/www/html/bin/command.php application:hermes_shutdown
继续关闭所有工作器需要用户确认。
如果您需要在无需用户交互的情况下运行此命令 (例如 CI、测试),请使用 --assume-yes
标志。
/usr/bin/php /var/www/html/bin/command.php application:hermes_shutdown --assume-yes
application:cleanup
该命令可用于清除从存储库中删除的数据,这些数据您不需要永久保存(即日志)。
默认情况下,该命令删除了基于 created_at
列的 2 个月前的数据。您可以通过(在您的项目配置文件中使用)更改默认阈值时间,以及命令删除旧存储库数据前使用的列
autoLoginTokensRepository: setup: - setRetentionThreshold('now', 'valid_at') changePasswordsLogsRepository: setup: - setRetentionThreshold('-12 months') userActionsLogRepository: setup: - setRetentionThreshold('-12 months')
组件
FrontendMenu
面向用户的前端菜单,预计将在您的应用程序布局中使用。
示例用法
通过以下方式在布局中使用
{control fronendMenu}
您可以在 config.local.neon
中覆盖默认的菜单布局
# ... services: frontendMenu: setup: - setTemplate('../../../../../app/modules/DemoModule/templates/frontend_menu.latte') # ...
FrontendMenuDataProviderInterface
dataproviders 必须实现此接口才能编辑 FrontendMenu
项。dataproviders 必须附加到 frontend_menu
dataproviders 占位符。
示例用法
在此示例中,DemoFrontendMenuDataProvider
通过链接从前端菜单中删除菜单项。
use Crm\ApplicationModule\DataProvider\DataProviderManager; class DemoModule { public function registerDataProviders(DataProviderManager $dataProviderManager) { $dataProviderManager->registerDataProvider( 'frontend_menu', $this->getInstance(DemoFrontendMenuDataProvider::class) ); } }
use Crm\ApplicationModule\DataProvider\DataProviderException; use Crm\ApplicationModule\DataProvider\FrontendMenuDataProviderInterface; use Crm\ApplicationModule\Menu\MenuContainerInterface; class DemoFrontendMenuDataProvider implements FrontendMenuDataProviderInterface { public function provide(array $params): void { if (!isset($params['menuContainer'])) { throw new DataProviderException('missing [menuContainer] within data provider params'); } /** @var MenuContainerInterface $menuContainer */ $menuContainer = $params['menuContainer']; $menuContainer->removeMenuItemByLink(':Invoices:Invoices:invoiceDetails'); } }
图表
以下是由 ApplicationModule
提供的各种图表集。
GoogleBarGraph
基于 Google 图表的简单条形图。通常用于 GoogleBarGraphGroup 组件中,但也可以单独使用,如果您需要手动管理您的组($data
参数中的键)。
示例用法
要使用图表,请在您的 presenter 或 widget 中创建类似的方法
namespace Crm\DemoModule\Presenters; class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter { // ... public function renderDefault() { } public function createComponentGoogleUserSubscribersRegistrationSourceStatsGraph() { $control = $this->factory->create(); $results = $this->database->table('subscriptions') ->where('subscriptions.start_time < ?', $this->database::literal('NOW()')) ->where('subscriptions.end_time > ?', $this->database::literal('NOW()')) ->group('user.source') ->select('user.source, count(*) AS count') ->order('count DESC') ->fetchAll(); $data = []; foreach ($results as $row) { $data[$row['source']] = $row['count']; } $control->addSerie($this->translator->translate('dashboard.users.active_sub_registrations.serie'), $data); return $control; } // ... }
在您的 templates/Demo/default.latte
模板中按需使用该组件
<div class="row"> <div class="col-md-12"> {control googleUserSubscribersRegistrationSourceStatsGraph} </div> </div>
GoogleBarGraphGroup
基于Google图表的简单柱状图。组件可以基于->setGroupBy
方法和查询结果创建多个组。内部使用GoogleBarGraph
来渲染图表。
示例
namespace Crm\DemoModule\Presenters; class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter { // ... public function renderDefault() { } public function createComponentGoogleUserActiveSubscribersRegistrationsSourceStatsGraph(GoogleBarGraphGroupControlFactoryInterface $factory) { $graphDataItem = new GraphDataItem(); $graphDataItem->setCriteria( (new Criteria)->setTableName('payments') ->setTimeField('created_at') ->setJoin('JOIN users ON payments.user_id = users.id') ->setWhere("AND payments.status = '" . PaymentsRepository::STATUS_PAID . "'") ->setGroupBy('users.source') // <-- THIS LINE DEFINES THE GROUPPING ->setSeries('users.source') // <-- THIS LINE DEFINES CHART SERIES ->setValueField('count(*)') ->setStart(DateTime::from($this->dateFrom)) ->setEnd(DateTime::from($this->dateTo)) ); $control = $factory->create(); $control->setGraphTitle($this->translator->translate('dashboard.payments.registration.title')) ->setGraphHelp($this->translator->translate('dashboard.payments.registration.tooltip')) ->addGraphDataItem($graphDataItem); return $control; }
在您的 templates/Demo/default.latte
模板中按需使用该组件
<div class="row"> <div class="col-md-12"> {control googleUserActiveSubscribersRegistrationsSourceStatsGraph} </div> </div>
GoogleLineGraph
基于Google图表的简单线形图。此组件仅由GoogleLineGraphGroup
使用,不建议直接使用,除非您确实需要为图表提供原始数据。
GoogleLineGraphGroup
基于Google图表的简单线形图。组件可以基于构建查询内部的数据组创建多个系列。
示例
namespace Crm\DemoModule\Presenters; class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter { // ... public function renderDefault() { } public function createComponentGoogleSubscriptionsEndGraph(GoogleLineGraphGroupControlFactoryInterface $factory) { $items = []; $graphDataItem = new GraphDataItem(); $graphDataItem->setCriteria((new Criteria()) ->setTableName('subscriptions') ->setTimeField('end_time') ->setValueField('count(*)') ->setStart($this->dateFrom) ->setEnd($this->dateTo)); $graphDataItem->setName($this->translator->translate('dashboard.subscriptions.ending.now.title')); $items[] = $graphDataItem; $graphDataItem = new GraphDataItem(); $graphDataItem->setCriteria((new Criteria()) ->setTableName('subscriptions') ->setWhere('AND next_subscription_id IS NOT NULL') ->setTimeField('end_time') ->setValueField('count(*)') ->setStart($this->dateFrom) ->setEnd($this->dateTo)); $graphDataItem->setName($this->translator->translate('dashboard.subscriptions.ending.withnext.title')); $items[] = $graphDataItem; $control = $factory->create() ->setGraphTitle($this->translator->translate('dashboard.subscriptions.ending.title')) ->setGraphHelp($this->translator->translate('dashboard.subscriptions.ending.tooltip')); foreach ($items as $graphDataItem) { $control->addGraphDataItem($graphDataItem); } return $control; } // ... }
在您的 templates/Demo/default.latte
模板中按需使用该组件
<div class="row"> <div class="col-md-12"> {control googleSubscriptionsEndGraph} </div> </div>
InlineBarGraph
内联柱状图旨在直接在网格中使用或与其他内联柱状图一起使用,以提供有关数据的快速信息。
示例
以下是在可用的支付网关列表网格中显示的多个内联柱状图的示例。
namespace Crm\DemoModule\Presenters; class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter { /** @var \Crm\ApplicationModule\Graphs\GraphData\GraphData @inject */ public $graphData; // ... public function renderDefault() { } public function createComponentSmallGraph() { return new Multiplier(function ($id) { $control = new InlineBarGraph; $graphDataItem = new GraphDataItem(); $graphDataItem ->setCriteria( (new Criteria()) ->setTableName('payments') ->setWhere('AND payment_gateway_id = ' . $id) ->setGroupBy('payment_gateway_id') ->setStart('-3 months') ); $this->graphData->clear(); $this->graphData->addGraphDataItem($graphDataItem); $this->graphData->setScaleRange('day'); $data = $this->graphData->getData(); if (!empty($data)) { $data = array_pop($data); } $control->setGraphTitle($this->translator->translate('payments.admin.payment_gateways.small_graph.title')) ->addSerie($data); return $control; }); } // ... }
在您的 templates/Demo/default.latte
模板中按需使用该组件
<div class="row"> {foreach $paymentGateways as $gateway} <div class="col-md-3"> {control smallGraph-$gateway->id} </div> {/foreach} </div>
SmallBarchart
SmallBarChart
旨在在CRM管理员界面中的小部件中使用,但也可以在面向用户的客户端中使用,以提供简单可计数的信息。
示例
namespace Crm\DemoModule\Presenters; class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter { // ... public function renderDefault() { } public function createComponentPaidPaymentsSmallBarGraph(SmallBarGraphControlFactoryInterface $factory) { return $this->generateSmallBarGraphComponent(PaymentsRepository::STATUS_PAID, 'Paid', $factory); } private function generateSmallBarGraphComponent($status, $title, SmallBarGraphControlFactoryInterface $factory) { $data = $this->paymentsHistogramFactory->paymentsLastMonthDailyHistogram($status); $control = $factory->create(); $control->setGraphTitle($title)->addSerie($data); return $control; } // ... }
在您的 templates/Demo/default.latte
模板中按需使用该组件
<div class="row"> <div class="col-md-3"> {control paidPaymentsSmallBarGraph} </div> </div>
GoogleSankeyGraphGroup
桑基图是基于Google桑基图的组件。它用于描述从一组值到另一组值的流动。
示例
namespace Crm\DemoModule\Presenters; class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter { // ... public function renderDefault() { } public function createComponentGoogleSankeyGraph(GoogleSankeyGraphGroupControlFactoryInterface $factory) { $graph = $factory->create(); $graph->setGraphHelp($this->translator->translate('Graph help'); $graph->setGraphTitle($this->translator->translate('Graph title'); $graph->setRows([ ['A', 'X', 1], ['A', 'Y', 3], ['A', 'Z', 2], ['B', 'X', 4], ['B', 'Y', 2], ['B', 'Z', 2], ]); $graph->setColumnNames('From', 'To', 'Count'); return $graph; } // ... }
在您的 templates/Demo/default.latte
模板中按需使用该组件
<div class="row"> <div class="col-md-12"> {control googleSankeyGraph} </div> </div>
VisualPaginator
分页器用于限制和偏移系统列表中显示的结果。分页器保持当前页面/限制并为您提供数据获取代码块的信息。
示例
以下是一个用于场景列表的分页器使用示例。
namespace Crm\DemoModule\Presenters; class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter { // ... public function renderDefault() { $scenarios = $this->scenariosRepository->all(); $filteredCount = $this->template->filteredCount = $products->count('*'); $vp = new VisualPaginator(); $this->addComponent($vp, 'scenarios_vp'); $paginator = $vp->getPaginator(); $paginator->setItemCount($filteredCount); $paginator->setItemsPerPage(50); $this->template->vp = $vp; $this->template->scenarios = $scenarios->limit($paginator->getLength(), $paginator->getOffset()); } // ... }
在您的templates/Demo/default.latte
模板中,根据需要使用组件(通常在列表下方或上方)。控件的名称应与您在->addComponent
第二个参数中使用的一致。
{control scenarios_vp}
小部件
以下是由ApplicationModule
提供的用于您的小部件的包装器集。应用程序提供三组包装器
SimpleWidget
简单小部件允许通过其他模块扩展模块的视图。模块可以在操作的模板(在.latte
文件中)中提供小部件的占位符,其他模块可以在它们的模块类中注册它们的小部件实现。
您可以在CRM骨架文档中了解更多关于创建和注册小部件的信息,该文档可在github.com/remp2020/crm-skeleton找到。
SingleStatWidget
小部件提供单个统计信息的简单表格的包装器 - 每个由单独的小部件实现提供。此使用的首选场景是仪表板。
数据库表迁移
由于需要更改主键(int -> bigint),在包含大量数据(或具有溢出int类型主键风险的)的表中,我们不得不创建迁移过程。由于某些表非常暴露,无法锁定超过几秒钟,我们决定创建新表,手动迁移数据,并在迁移过程中保持旧表和新表的同步。
此迁移过程仅适用于特定版本后特定表的安装,并且是两步过程。
审计日志迁移(安装前2.5.0)
包括audit_logs
表的迁移。
步骤
- 运行Phinx迁移命令
phinx:migrate
,它创建新表audit_logs_v2
(如果没有表中的数据,迁移只需更改主键类型,以下步骤就不需要了)。 - 运行命令
application:convert_audit_logs_to_bigint
,该命令将数据从旧表复制到新表(例如,从audit_logs
到audit_logs_v2
)。在迁移成功后,该命令将原子性地重命名表(例如,从audit_logs
到audit_logs_old
和从audit_logs_v2
到audit_logs
),因此当迁移结束时,只使用新表。
建议在迁移成功后至少运行application:bigint_migration_cleanup audit_logs
命令2周(以保留备份数据,以防出现某些问题),然后删除多余的表。