spryker-demo / company-document-feature
Company Document Feature
1.0.0
2025-04-09 16:09 UTC
Requires
- php: >=8.2
- spryker-demo/company-document: ^1.0.0
- spryker-demo/company-document-page: ^1.0.0
- spryker-demo/company-document-storage: ^1.0.0
Requires (Dev)
- spryker/code-sniffer: ^0.17.18
README
This feature handles company document functionality.
Install the required modules using Composer
composer require spryker-demo/company-document-feature
Add SprykerDemo
namespace to configuration
$config[KernelConstants::CORE_NAMESPACES] = [
...
'SprykerDemo',
];
Add translations
# data/import/common/common/glossary.csv
company.account.company_documents,Documents,en_US
company.account.company_documents,Dokumente,de_DE
company.account.company_documents,Documents,fr_FR
company.account.company_documents,Documentos,es_ES
company.account.company_document.name,Document name,en_US
company.account.company_document.name,Dokumentenname,de_DE
company.account.company_document.name,Nom du document,fr_FR
company.account.company_document.name,Nombre del documento,es_ES
company.account.company_document.upload_date,Upload Date,en_US
company.account.company_document.upload_date,Datum des Uploads,de_DE
company.account.company_document.upload_date,Date de téléchargement,fr_FR
company.account.company_document.upload_date,Fecha de subida,es_ES
company.account.company_document.download,Download,en_US
company.account.company_document.download,Download,de_DE
company.account.company_document.download,Télécharger,fr_FR
company.account.company_document.download,Descargar,es_ES
company.account.company_document.no_document.,No documents available at the moment,en_US
company.account.company_document.no_document.,Aktuell keine Dokumente verfügbar,de_DE
company.account.company_document.no_document.,Aucun document disponible pour le moment,fr_FR
company.account.company_document.no_document.,No hay documentos disponibles por el momento,es_ES
Import translations
console data:import:glossary
Set up behavior
Activate the following plugins:
src/Pyz/Yves/Router/RouterDependencyProvider.php
... use SprykerDemo\Yves\CompanyDocumentPage\Plugin\Router\CompanyDocumentPageRouteProviderPlugin; ... class RouterDependencyProvider extends SprykerRouterDependencyProvider { ... /** * @return array<\Spryker\Yves\RouterExtension\Dependency\Plugin\RouteProviderPluginInterface> */ protected function getRouteProvider(): array { return [ new CompanyDocumentPageRouteProviderPlugin(), ]; } }
src/Pyz/Client/RabbitMq/RabbitMqConfig.php
... use SprykerDemo\Yves\CompanyDocumentPage\Plugin\Router\CompanyDocumentPageRouteProviderPlugin; ... class RouterDependencyProvider extends SprykerRouterDependencyProvider { ... protected function getRouteProvider(): array { return [ new CompanyDocumentPageRouteProviderPlugin(), ]; } }
src/Pyz/Client/RabbitMq/RabbitMqConfig.php
... use SprykerDemo\Yves\CompanyDocumentPage\Plugin\Router\CompanyDocumentPageRouteProviderPlugin; ... class RabbitMqConfig extends SprykerRabbitMqConfig { ... protected function getPyzPublishQueueConfiguration(): array { return [ ... CompanyDocumentStorageConfig::PUBLISH_COMPANY_DOCUMENT_QUEUE, ]; } ... protected function getPyzSynchronizationQueueConfiguration(): array { return [ ... CompanyDocumentStorageConfig::SYNC_STORAGE_COMPANY_DOCUMENT_QUEUE, ]; } }
src/Pyz/Zed/Publisher/PublisherDependencyProvider.php
... use SprykerDemo\Shared\CompanyDocumentStorage\CompanyDocumentStorageConfig; use SprykerDemo\Zed\CompanyDocumentStorage\Communication\Plugin\Publisher\CompanyDocument\CompanyDocumentDeletePublisherPlugin; use SprykerDemo\Zed\CompanyDocumentStorage\Communication\Plugin\Publisher\CompanyDocument\CompanyDocumentWritePublisherPlugin; ... class PublisherDependencyProvider extends SprykerPublisherDependencyProvider { ... protected function getPublisherPlugins(): array { return array_merge( ... $this->getCompanyDocumentStoragePlugins(), ); } ... protected function getCompanyDocumentStoragePlugins(): array { return [ CompanyDocumentStorageConfig::PUBLISH_COMPANY_DOCUMENT_QUEUE => [ new CompanyDocumentWritePublisherPlugin(), new CompanyDocumentDeletePublisherPlugin(), ], ]; } }
src/Pyz/Zed/Queue/QueueDependencyProvider.php
... use SprykerDemo\Shared\CompanyDocumentStorage\CompanyDocumentStorageConfig; ... class QueueDependencyProvider extends SprykerDependencyProvider { ... protected function getProcessorMessagePlugins(Container $container): array { return [ ... CompanyDocumentStorageConfig::PUBLISH_COMPANY_DOCUMENT_QUEUE => new EventQueueMessageProcessorPlugin(), CompanyDocumentStorageConfig::SYNC_STORAGE_COMPANY_DOCUMENT_QUEUE => new SynchronizationStorageQueueMessageProcessorPlugin(), ]; } }
src/Pyz/Zed/Publisher/PublisherDependencyProvider.php
... use SprykerDemo\Zed\CompanyDocumentStorage\Communication\Plugin\Publisher\CompanyDocument\CompanyDocumentPublisherTriggerPlugin ... class PublisherDependencyProvider extends SprykerPublisherDependencyProvider { ... protected function getPublisherTriggerPlugins(): array { return [ ... new CompanyDocumentPublisherTriggerPlugin(), ]; }
src/Pyz/Zed/Synchronization/SynchronizationDependencyProvider.php
... use SprykerDemo\Zed\CompanyDocumentStorage\Communication\Plugin\Synchronization\CompanyDocument\CompanyDocumentSynchronizationDataBulkRepositoryPlugin; ... class SynchronizationDependencyProvider extends SprykerSynchronizationDependencyProvider { ... protected function getSynchronizationDataBulkPlugins(): array { return [ ... new CompanyDocumentSynchronizationDataBulkRepositoryPlugin(), ]; } }
Extend CompanyDocumentStorageConfig:
src/Pyz/Zed/CompanyDocumentStorage/CompanyDocumentStorageConfig.php
namespace Pyz\Zed\CompanyDocumentStorage; use Pyz\Zed\Synchronization\SynchronizationConfig; use SprykerDemo\Zed\CompanyDocumentStorage\CompanyDocumentStorageConfig as SprykerDemoCompanyDocumentStorageConfig; class CompanyDocumentStorageConfig extends SprykerDemoCompanyDocumentStorageConfig { ... /** * @api * * @return string|null */ public function getSynchronizationQueuePoolName(): ?string { return SynchronizationConfig::DEFAULT_SYNCHRONIZATION_POOL_NAME; } }
Apply Twig customization
# src/Pyz/Yves/CustomerPage/Theme/default/components/molecules/customer-navigation/customer-navigation.twig
{% extends model('component') %}
{% import _self as component %}
{% define config = {
name: 'customer-navigation',
tag: 'nav'
} %}
{% define data = {
activePage: required,
activeEntityId: required
} %}
{% block body %}
{% if can('SeeCompanyMenuPermissionPlugin') %}
{% include molecule('customer-navigation-sidebar', 'CustomerPage') with {
modifiers: ['company'],
data: {
title: 'customer.menu.company.title' | trans,
activePage: data.activePage,
items: [
{
name: 'overview',
url: path('company/overview'),
label: 'company.account.overview' | trans,
icon: 'shopping-list'
},
{
name: 'users',
url: path('company/user'),
label: 'company.account.company_user' | trans,
icon: 'users'
},
{
name: 'business-unit',
url: path('company/business-unit'),
label: 'company.account.business_unit' | trans,
icon: 'office'
},
{
name: 'role',
url: path('company/company-role'),
label: 'company.account.company_role' | trans,
icon: 'user'
},
{
name: 'company-documents',
url: path('company/documents'),
label: 'company.account.company_documents' | trans,
icon: 'file',
},
]
}
} only %}
{% endif %}
{% embed molecule('customer-navigation-sidebar', 'CustomerPage') with {
modifiers: ['customer'],
data: {
title: 'customer.menu.customer.title' | trans,
activePage: data.activePage,
activeEntityId: data.activeEntityId,
items: [
{
name: 'customer-overview',
url: path('customer/overview'),
label: 'customer.account.overview' | trans,
icon: 'page',
},
{
name: 'profile',
url: path('customer/profile'),
label: 'customer.account.profile_data' | trans,
icon: 'user',
},
{
name: 'address',
url: path('customer/address'),
label: 'customer.account.address' | trans,
icon: 'marker',
},
{
name: 'order',
url: path('customer/order'),
label: 'customer.account.order_history' | trans,
icon: 'history',
},
{
name: 'subscription',
url: path('subscription-product'),
label: 'customer.account.subscriptions' | trans,
icon: 'subscription',
},
{
name: 'return',
url: path('return/list'),
label: 'return_page.list.title' | trans,
icon: 'return-arrow',
isActiveItem: 'return/list' == data.activePage | default,
},
{
name: 'newsletter',
url: path('customer/newsletter'),
label: 'customer.account.newsletter' | trans,
icon: 'envelopes',
},
]
}
} only %}
{% block postContent %}
{% widget 'ShoppingListMenuItemWidget' args [
data.activePage,
data.activeEntityId,
] only %}
{% endwidget %}
{% widget 'MultiCartMenuItemWidget' args [data.activePage] only %}{% endwidget %}
{% widget 'QuoteRequestMenuItemWidget' args [data.activePage] only %}{% endwidget %}
{% widget 'BusinessOnBehalfStatusWidget' use view('change-company-user', 'BusinessOnBehalfWidget') with {
isActivePage: 'change-company-user' == data.activePage
} only %}
{% block body %}
{% include molecule('navigation-sidebar-item') with {
data: {
url: path('company/user/select'),
icon: 'building',
label: 'business_on_behalf_widget.no_selected_company' | trans,
name: 'no-company',
active: isActivePage,
},
modifiers: isActivePage ? ['active'] : [],
} only %}
{% endblock %}
{% endwidget %}
{% endblock %}
{% endembed %}
{% endblock %}
# src/Pyz/Yves/ShopUi/Theme/default/components/molecules/user-navigation/user-navigation.twig
{% extends model('component') %}
{% define config = {
name: 'user-navigation',
tag: 'ul',
} %}
{% define data = {
user: required,
} %}
{% set cartQuantityText = cartQuantity > 0 ? cartQuantity ~ ' ' %}
{% set shouldShowMiniCart = findWidget('MiniCartWidget', [cartQuantityText]).isMultiCartAllowed %}
{%- block class -%}
{{ parent() }} {{ config.jsName }}
{%- endblock -%}
{% block body %}
<li class="{{ config.name }}__item {{ config.name }}__item--user">
<div class="{{ config.name }}__user-name">
<span class="{{ config.name }}__icon">
{% include atom('icon') with {
modifiers: ['user'],
data: {
name: 'user',
},
} only %}
{% include atom('icon') with {
class: config.name ~ '__arrow',
data: {
name: 'caret-down',
},
} only %}
</span>
<span class="{{ config.name }}__text">{% widget 'CustomerFullNameWidget' only %}{% endwidget %}</span>
</div>
{% if can('SeeCompanyMenuPermissionPlugin') %}
{% include molecule('navigation-list') with {
modifiers: ['secondary'],
class: config.name ~ '__sub-nav',
data: {
nodes: [
{
url: path('company/overview'),
title: 'company.account.overview' | trans,
},
{
url: path('company/user'),
title: 'company.account.company_user' | trans,
},
{
url: path('company/business-unit'),
title: 'company.account.business_unit' | trans,
},
{
url: path('company/company-role'),
title: 'company.account.company_role' | trans,
},
{
url: path('company/documents'),
title: 'company.account.company_documents' | trans,
cssItemClass: 'has-separator',
},
{
url: url('customer/overview'),
title: 'customer.account.overview' | trans,
},
{
url: url('customer/profile'),
title: 'customer.account.profile_data' | trans,
},
{
url: url('customer/address'),
title: 'customer.account.address' | trans,
},
{
url: url('customer/order'),
title: 'customer.account.order_history' | trans,
},
{
url: url('subscription-product'),
title: 'customer.account.subscriptions' | trans,
},
{
url: url('return/list'),
title: 'return_page.default_title' | trans,
},
{
url: url('customer/newsletter'),
title: 'customer.account.newsletter' | trans,
},
{
url: url('quote-request'),
title: 'quote_request_widget.request_for_quote.list.title' | trans,
},
{
url: url('logout'),
title: 'customer.logout' | trans,
additionalClass: 'link--additional',
},
]
},
} only %}
{% else %}
{% include molecule('navigation-list') with {
modifiers: ['secondary'],
class: config.name ~ '__sub-nav',
data: {
nodes: [
{
url: url('customer/overview'),
title: 'customer.account.overview' | trans,
},
{
url: url('customer/profile'),
title: 'customer.account.profile_data' | trans,
},
{
url: url('customer/address'),
title: 'customer.account.address' | trans,
},
{
url: url('customer/order'),
title: 'customer.account.order_history' | trans,
},
{
url: url('subscription-product'),
title: 'customer.account.subscriptions' | trans,
},
{
url: url('return/list'),
title: 'return_page.default_title' | trans,
},
{
url: url('customer/newsletter'),
title: 'customer.account.newsletter' | trans,
},
{
url: url('quote-request'),
title: 'quote_request_widget.request_for_quote.list.title' | trans,
},
{
url: url('logout'),
title: 'customer.logout' | trans,
additionalClass: 'link--additional',
},
]
},
} only %}
{% endif %}
</li>
<li class="{{ config.name }}__item">
<a class="{{ config.name }}__link" href="{{ url('quick-order') }}">
<span class="{{ config.name }}__icon">
{% include atom('icon') with {
modifiers: ['quick-order'],
data: {
name: 'quick-order',
},
} only %}
</span>
<span class="{{ config.name }}__text">{{ 'quick-order.page-title'|trans }}</span>
</a>
</li>
{% widget 'ShoppingListNavigationMenuWidget' with {config: config} only %}
{% block body %}
<li class="{{ config.name }}__item">
<a class="{{ config.name }}__link {{ config.jsName }}__trigger" href="{{ url('shopping-list') }}" data-toggle-target='.{{ config.jsName }}__sub-nav-shopping-list'>
<span class="{{ config.name }}__icon">
{% include atom('icon') with {
modifiers: ['header-shopping-list'],
data: {
name: 'shopping-list',
},
} only %}
</span>
<span class="{{ config.name }}__text">{{ 'customer.account.shopping_list'|trans }}</span>
</a>
<div class="{{ config.name }}__sub-nav {{ config.name }}__sub-nav--pull-left spacing spacing--inner spacing--reset {{ config.jsName }}__sub-nav-shopping-list">
<button class="{{config.name}}__sub-nav-close {{ config.jsName }}__trigger" data-toggle-target='.{{ config.jsName }}__sub-nav-shopping-list'>
{% include atom('icon') with {
data: {
name: 'cross',
},
} only %}
</button>
<h4 class="{{ config.name }}__sub-nav-title">
{{ 'shopping_list.shopping_list'|trans }}
</h4>
{% block listItems %}
<div class="{{ config.name }}__sub-nav-inner">
{{ parent() }}
</div>
{% endblock %}
{% block listActions %}
<div class="{{ config.name }}__sub-nav-actions">
{{ parent() }}
</div>
{% endblock %}
</div>
</li>
{% endblock %}
{% endwidget %}
<li class="{{ config.name }}__item">
{% if not shouldShowMiniCart %}
<a class="{{ config.name }}__link" href="{{ url('cart') }}">
<span class="{{ config.name }}__empty-cart">
{% include atom('icon') with {
modifiers: ['cart'],
data: {
name: 'cart',
},
} only %}
</span>
<span>{{ 'global.my-cart' | trans }}</span>
</a>
{% else %}
{% embed molecule('cart-counter') with {
class: config.jsName ~ '__trigger',
data: {
quantity: cartQuantity,
},
attributes: {
'data-toggle-target': '.' ~ config.jsName ~ '__sub-nav-cart',
},
embed: {
parentConfigName: config.name,
},
} only %}
{% block content %}
{% set linkClass = embed.parentConfigName ~ '__link' %}
{{ parent() }}
{% endblock %}
{% endembed %}
<div class="{{ config.name }}__sub-nav {{ config.name }}__sub-nav--pull-left spacing spacing--inner spacing--reset {{ config.jsName }}__sub-nav-cart">
{% widget 'MiniCartWidget' args [cartQuantityText] with {config: config} only %}
{% block body %}
<button class="{{config.name}}__sub-nav-close {{ config.jsName }}__trigger" data-toggle-target='.{{ config.jsName }}__sub-nav-cart'>
{% include atom('icon') with {
data: {
name: 'cross',
},
} only %}
</button>
<h4 class="{{ config.name }}__sub-nav-title">
{{ 'multi_cart_widget.cart.carts'|trans }}
</h4>
{% block cartItems %}
<div class="{{ config.name }}__sub-nav-inner">
{{ parent() }}
</div>
{% endblock %}
{% block cartActions %}
<div class="{{ config.name }}__sub-nav-actions">
{{ parent() }}
</div>
{% endblock %}
{% endblock %}
{% endwidget %}
</div>
{% endif %}
</li>
{% include molecule('toggler-accordion') with {
attributes: {
'wrap-class-name': config.jsName,
'trigger-class-name': config.jsName ~ '__trigger',
'class-to-toggle': config.name ~ '__sub-nav--touch-active',
'active-on-touch': 'true',
},
} only %}
{% endblock %}
# src/Pyz/Yves/ShopUi/Theme/default/templates/page-layout-main/page-layout-main.twig
{% extends template('page-blank') %}
{% block meta %}
{{ parent() }}
<meta name="format-detection" content="telephone=no">
{% endblock %}
{% block headStyles %}
{{ parent() }}
{% if findWidget('FrontendConfiguratorWidget') %}
{% widget 'FrontendConfiguratorWidget' only %}{% endwidget %}
{% endif %}
{% endblock %}
{%- block class -%}js-page-layout-main__side-drawer-container{%- endblock -%}
{% block body %}
{% if findWidget('FrontendConfiguratorWidget') %}
{% set frontendConfiguratorWidget = findWidget('FrontendConfiguratorWidget') %}
{% endif %}
{% block notifications %}
{% include organism('notification-area') only %}
{% endblock %}
<div class="page-layout page-layout--preload">
{% widget 'AgentControlBarWidget' only %}{% endwidget %}
{% block sidebar %}
{% embed organism('side-drawer') with {
attributes: {
'container-class-name': 'js-page-layout-main__side-drawer-container',
'trigger-class-name': 'js-page-layout-main__side-drawer-trigger',
'locked-body-class-name': 'is-locked-tablet',
},
} only %}
{% block navigation %}
{% cms_slot 'slt-mobile-header' %}
{% endblock %}
{% endembed %}
{% if is_granted('ROLE_USER') %}
{% set customerFullNameWidget = findWidget('CustomerFullNameWidget') %}
{% set customerFullName = customerFullNameWidget.customerFullName %}
{% include organism('account-navigation') with {
attributes: {
'container-class-name': 'js-page-layout-main__side-drawer-container',
'trigger-class-name': 'js-page-layout-main__user-account-navigation-trigger',
'locked-body-class-name': 'is-locked-mobile',
},
data: {
title: customerFullName,
items: [
{
name: 'overview',
url: path('customer/overview'),
label: 'customer.account.overview' | trans,
icon: 'page'
},
{
name: 'profile',
url: path('customer/profile'),
label: 'customer.account.profile_data' | trans,
icon: 'user'
},
{
name: 'address',
url: path('customer/address'),
label: 'customer.account.address' | trans,
icon: 'marker'
},
{
name: 'order',
url: path('customer/order'),
label: 'customer.account.order_history' | trans,
icon: 'history'
},
{
name: 'returns',
url: path('return/list'),
label: 'return_page.default_title' | trans,
icon: 'return-arrow'
},
{
name: 'newsletter',
url: path('customer/newsletter'),
label: 'customer.account.newsletter' | trans,
icon: 'envelopes'
},
{
name: 'shopping-list',
url: path('shopping-list'),
label: 'customer.account.shopping_list' | trans,
icon: 'shopping-list'
},
{
name: 'shopping-cart',
url: path('multi-cart'),
label: 'page.multi_cart.shopping_cart.list.title' | trans,
icon: 'cart'
},
{
name: 'quote-request',
url: path('quote-request'),
label: 'quote_request_widget.request_for_quote.list.title' | trans,
icon: 'message',
},
],
}
} only %}
{% if can('SeeCompanyMenuPermissionPlugin') %}
{% set menuItemCompanyWidget = findWidget('MenuItemCompanyWidget') %}
{% set companyName = menuItemCompanyWidget.companyName %}
{% include organism('account-navigation') with {
modifiers: ['company'],
attributes: {
'container-class-name': 'js-page-layout-main__side-drawer-container',
'trigger-class-name': 'js-page-layout-main__company-account-navigation-trigger',
'locked-body-class-name': 'is-locked-mobile',
},
data: {
introIcon: 'office',
title: companyName,
items: [
{
name: 'overview',
url: path('company/overview'),
label: 'company.account.overview' | trans,
icon: 'shopping-list'
},
{
name: 'users',
url: path('company/user'),
label: 'company.account.company_user' | trans,
icon: 'users'
},
{
name: 'business-unit',
url: path('company/business-unit'),
label: 'company.account.business_unit' | trans,
icon: 'office'
},
{
name: 'role',
url: path('company/company-role'),
label: 'company.account.company_role' | trans,
icon: 'user'
},
{
name: 'company-documents',
url: path('company/documents'),
label: 'company.account.company_documents' | trans,
icon: 'file',
}
]
}
} only %}
{% endif %}
{% endif %}
{% endblock %}
{% block outside %}{% endblock %}
{% block header %}
{% embed organism('header') with {
embed: {
logoSrc: frontendConfiguratorWidget.data.logoUrl ?? null,
},
} only %}
{% block navigation %}
{% cms_slot 'slt-desktop-header' %}
{% endblock %}
{% block logo %}
{% include molecule('logo') with {
class: 'col ' ~ config.name ~ '__logo',
modifiers: ['main'],
attributes: {
src: embed.logoSrc,
}
} only %}
{% endblock %}
{% block mobile %}
<a href="#" class="link link--alt js-page-layout-main__side-drawer-trigger">
{% include atom('icon') with {
modifiers: ['big'],
data: {
name: 'bars',
},
} only %}
</a>
{% endblock %}
{% endembed %}
{% endblock %}
<div itemscope itemtype="https://schema.org/Product" class="content-wrap">
{% block pageInfo %}
<div class="page-info">
<div class="container">
{% block breadcrumbs %}
{% include molecule('breadcrumb') only %}
{% endblock %}
{% block title %}
<h1 class="page-info__title title title--h3 ">{{ data.title }}</h1>
{% endblock %}
</div>
</div>
{% endblock %}
<main class="{% block contentClass %}{% endblock %}">
{% block contentTop %}{% endblock %}
{% block contentWrap %}
<div class="container">
{% block content %}{% endblock %}
</div>
{% endblock %}
{% block contentBottom %}{% endblock %}
</main>
</div>
{% block footer %}
{% cms_slot 'slt-1' %}
{% embed organism('footer') only %}
{% block logosInner %}
{% cms_slot 'slt-footer-partners' %}
{% endblock %}
{% block navigationInner %}
<div class="grid grid--gap">
{% cms_slot 'slt-footer-navigation' %}
</div>
{% endblock %}
{% block socialLinks %}
{% cms_slot 'slt-footer-social-links' %}
{% endblock %}
{% endembed %}
{% endblock %}
</div>
{% include atom('overlay-block') only %}
{% include atom('touch-checker') only %}
{% block globalComponents %}
{% include molecule('action-single-click-enforcer') with {
attributes: {
'target-selector': '[data-init-single-click]',
},
} only %}
{% include molecule('form-submitter') with {
attributes: {
'trigger-selector': '[data-form-submitter]',
},
} only %}
{% include molecule('viewport-intersection-observer') only %}
{% include molecule('node-animator') only %}
{% include molecule('window-load-class-remover') with {
attributes: {
'target-class-name': 'page-layout',
'trigger-class-name': 'page-layout--preload',
},
} only %}
{% endblock %}
{% block icons %}
{% include atom('icon-sprite') only %}
{% endblock %}
{% endblock %}