remp/crm-application-module

3.4.0 2024-09-18 10:57 UTC

README

Translation status @ Weblate

配置

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')
# ...
预览

alt text

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>
预览

alt text

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>
预览

alt text

GoogleLineGraph

基于Google图表的简单线形图。此组件仅由GoogleLineGraphGroup使用,不建议直接使用,除非您确实需要为图表提供原始数据。

预览

alt text

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>
预览

alt text

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>
预览

alt text

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>
预览

alt text

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>
预览

alt text

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}
预览

alt text

小部件

以下是由ApplicationModule提供的用于您的小部件的包装器集。应用程序提供三组包装器

SimpleWidget

简单小部件允许通过其他模块扩展模块的视图。模块可以在操作的模板(在.latte文件中)中提供小部件的占位符,其他模块可以在它们的模块类中注册它们的小部件实现。

您可以在CRM骨架文档中了解更多关于创建和注册小部件的信息,该文档可在github.com/remp2020/crm-skeleton找到。

SingleStatWidget

小部件提供单个统计信息的简单表格的包装器 - 每个由单独的小部件实现提供。此使用的首选场景是仪表板。

预览

alt text

数据库表迁移

由于需要更改主键(int -> bigint),在包含大量数据(或具有溢出int类型主键风险的)的表中,我们不得不创建迁移过程。由于某些表非常暴露,无法锁定超过几秒钟,我们决定创建新表,手动迁移数据,并在迁移过程中保持旧表和新表的同步。

此迁移过程仅适用于特定版本后特定表的安装,并且是两步过程。

审计日志迁移(安装前2.5.0)

包括audit_logs表的迁移。

步骤

  1. 运行Phinx迁移命令phinx:migrate,它创建新表audit_logs_v2(如果没有表中的数据,迁移只需更改主键类型,以下步骤就不需要了)。
  2. 运行命令application:convert_audit_logs_to_bigint,该命令将数据从旧表复制到新表(例如,从audit_logsaudit_logs_v2)。在迁移成功后,该命令将原子性地重命名表(例如,从audit_logsaudit_logs_old和从audit_logs_v2audit_logs),因此当迁移结束时,只使用新表。

建议在迁移成功后至少运行application:bigint_migration_cleanup audit_logs命令2周(以保留备份数据,以防出现某些问题),然后删除多余的表。