PHPackages                             orangecat/module-prices-company - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. orangecat/module-prices-company

ActiveMagento2-module

orangecat/module-prices-company
===============================

Per-company, per-SKU, quantity-aware custom pricing rules for Magento 2 B2B

0.0.2(2d ago)02↑2900%OSL-3.0PHPPHP &gt;=8.1

Since Jun 5Pushed 2d agoCompare

[ Source](https://github.com/olivertar/m2_pricescompany)[ Packagist](https://packagist.org/packages/orangecat/module-prices-company)[ RSS](/packages/orangecat-module-prices-company/feed)WikiDiscussions main Synced 2d ago

READMEChangelog (2)Dependencies (4)Versions (3)Used By (0)

Orangecat\_PricesCompany
========================

[](#orangecat_pricescompany)

Per-company, per-SKU, quantity-aware custom pricing rules for Magento 2 B2B.

**Module:** `Orangecat_PricesCompany`**Version:** 1.0.0 **License:** OSL-3.0 **Author:** Oliverio Gombert

---

Table of Contents
-----------------

[](#table-of-contents)

1. [Overview](#overview)
2. [Theme Compatibility](#theme-compatibility)
3. [Requirements](#requirements)
4. [Installation](#installation)
5. [What Gets Installed](#what-gets-installed)
6. [Configuration](#configuration)
7. [Store Admin Guide](#store-admin-guide)
8. [Developer Guide](#developer-guide)
9. [REST API](#rest-api)
10. [Frontend Routes Reference](#frontend-routes-reference)
11. [DevOps &amp; Integrator Notes](#devops--integrator-notes)

---

Overview
--------

[](#overview)

`Orangecat_PricesCompany` attaches company-specific pricing rules directly to a company entity. Each rule targets a single SKU at a minimum purchase quantity and applies one of three discount strategies: a fixed final price, a flat amount off, or a percentage off. Rules are quantity-tiered: the highest-matching threshold applies, enabling volume pricing per company.

The module registers a `PricesCompanyCalculator` into the `Orangecat_Prices` orchestrator pool. The orchestrator calls every registered calculator in sequence; this module's calculator returns a price (or `null` if no rule matches) and the orchestrator applies it to the product.

Responsibilities:

- Store per-company, per-SKU, per-quantity pricing rules in a dedicated flat table
- Register a price calculator into the `Orangecat_Prices` orchestrator pool
- Expose an Admin UI grid to browse companies and manage their pricing rules
- Support adding products via an interactive AJAX modal with per-row discount configuration
- Expose a REST API for programmatic bulk upsert, search, calculation, and deletion
- Support bulk CSV import/export via Magento's native ImportExport framework

### Position in the Orangecat B2B Dependency Chain

[](#position-in-the-orangecat-b2b-dependency-chain)

```
Orangecat_Core (via composer: orangecat/core)
  └── Orangecat_Company
        └── Orangecat_Prices          (price orchestrator and calculator pool)
              └── Orangecat_PricesCompany   ← this module

```

### Price Resolution Strategy

[](#price-resolution-strategy)

When the Prices orchestrator resolves the final price for a product, it passes a `$basePrice` to each registered calculator. This module's behavior depends on the **Price Resolution Strategy** setting:

ModeDescription**Overwrite** (default)The company rule is applied against the catalog base price, ignoring any upstream discount (e.g., from `PricesList`). Company rule wins absolutely.**Stack**The `$basePrice` received is already adjusted by earlier calculators (e.g., a `PricesList` discount). The company rule stacks on top, compounding discounts.### Discount Types

[](#discount-types)

ValueLabelCalculation`fixed_price`Fixed Price OverrideFinal price = `amount` (ignores base price)`fixed_amount`Fixed Amount DiscountFinal price = `base_price - amount``percentage`Percentage DiscountFinal price = `base_price × (1 - amount/100)`For `fixed_amount` and `percentage`, the `base_price` used is determined by the resolution mode (see above). The calculated price is floored at `0.0` and never goes negative.

### Tier Pricing

[](#tier-pricing)

Multiple rules for the same company + SKU at different `qty` thresholds create volume tiers. The calculator picks the rule with the highest `qty` that is ≤ the requested quantity. The `getTiers()` method returns all tier breakpoints (qty &gt; 1) for display on the product page.

---

Theme Compatibility
-------------------

[](#theme-compatibility)

ThemeStatusNotes**Luma**SupportedNo frontend templates. Pricing applies transparently via the Prices orchestrator.**Hyvä**SupportedNo frontend templates. Pricing applies transparently via the Prices orchestrator.**Breeze Evolution**SupportedNo frontend templates. Pricing applies transparently via the Prices orchestrator.This module has no frontend views. All pricing logic runs server-side inside the orchestrator pool and surfaces through the standard catalog price layer, making it theme-agnostic.

---

Requirements
------------

[](#requirements)

DependencyVersion / NotesMagento 22.4.xPHP≥ 8.1`orangecat/core`composer dependency`Orangecat_Company`must be enabled (provides `mycompany` table and `CompanyRepositoryInterface`)`Orangecat_Prices`must be enabled (provides the `CalculatorPool` injection point)`Magento_ImportExport`for CSV import support`Magento_Catalog`for product lookup during import and calculation---

Installation
------------

[](#installation)

This module ships as a git submodule of the main B2B SDK repository.

```
# From the repo root (if not already initialized)
git submodule update --init app/code/Orangecat/PricesCompany

# Inside the PHP container
bin/magento module:enable Orangecat_PricesCompany
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f
bin/magento cache:flush
```

---

What Gets Installed
-------------------

[](#what-gets-installed)

### Database Tables

[](#database-tables)

**`pricescompany_item`** — Flat table storing per-company pricing rules.

ColumnTypeNotes`item_id`INT UNSIGNED AUTO\_INCREMENTPrimary key`company_id`INT UNSIGNED NOT NULLFK → `mycompany.entity_id` ON DELETE CASCADE`sku`VARCHAR(64) NOT NULLProduct SKU`qty`DECIMAL(12,4) NOT NULL DEFAULT 1.0000Minimum quantity threshold for this rule`discount_type`VARCHAR(32) NOT NULL DEFAULT `fixed_price`One of: `fixed_price`, `fixed_amount`, `percentage``amount`DECIMAL(12,4) NOT NULL DEFAULT 0.0000Price, flat discount amount, or percentage value`created_at`TIMESTAMPSet on insert`updated_at`TIMESTAMPUpdated automatically on every writeConstraints and indexes:

- **Unique**: `(company_id, sku, qty)` — only one rule per company/SKU/quantity combination
- **Index**: `company_id` (btree), `sku` (btree)
- **Foreign key**: `company_id → mycompany.entity_id` with `ON DELETE CASCADE`

### EAV Attributes

[](#eav-attributes)

None.

### Data Patches

[](#data-patches)

None. No default records are created on install.

---

Configuration
-------------

[](#configuration)

Settings live under **Stores &gt; Configuration &gt; Prices &gt; Company Custom Prices**.

### Company Custom Prices (`prices/pricescompany`)

[](#company-custom-prices-pricespricescompany)

LabelConfig pathDefaultDescriptionEnable Custom Company Prices`prices/pricescompany/enabled`YesMaster switch. When disabled, the calculator always returns `null` and has no effect on pricing.Price Resolution Strategy`prices/pricescompany/resolution_mode``overwrite`Controls what `base_price` is passed to the calculator. See [Price Resolution Strategy](#price-resolution-strategy). Only visible when enabled = Yes.```
prices/pricescompany/enabled
prices/pricescompany/resolution_mode

```

The `resolution_mode` field accepts two values: `overwrite` and `stack`.

---

Store Admin Guide
-----------------

[](#store-admin-guide)

### Navigation

[](#navigation)

**Catalog &gt; Manage Company Prices**

This menu item opens the Company Prices index, a grid listing all companies from the `mycompany` table.

### Company Prices Index

[](#company-prices-index)

The grid shows every company with columns for company name and ID. From here you can:

- **Click a company row** (or the *Edit* action) to open that company's pricing rules.
- Use the grid's **search and filter** tools to locate a specific company.

### Company Edit Screen — Pricing Rules Grid

[](#company-edit-screen--pricing-rules-grid)

Opening a company shows its item grid: all custom pricing rules for that company.

Available columns in the item grid:

ColumnDescriptionSKUProduct SKU the rule applies toQtyMinimum quantity thresholdDiscount Type`Fixed Price Override`, `Fixed Amount Discount`, or `Percentage Discount`AmountPrice, deduction amount, or percentage depending on type**Adding rules — "Add Products" button:**

1. Click **Add Products**. A product selector modal opens (standard Magento catalog grid).
2. Select one or more products and click the Add button.
3. A pricing dialog appears with one row per selected product, pre-filled with the catalog price.
4. For each row, choose the **Discount Type** and enter the **Amount**. Optionally adjust the **Qty** threshold (defaults to 1).
5. Click **Confirm &amp; Add**. The rules are saved immediately via AJAX and the item grid reloads.

**Inline editing:**

Double-click any cell in the `Qty`, `Amount`, or `Discount Type` columns to edit it inline. Changes save automatically on blur.

**Deleting rules:**

- Click the *Delete* action on a single row.
- Select multiple rows and use **Actions &gt; Delete** for mass deletion.

### CSV Import

[](#csv-import)

Navigate to **System &gt; Import**, select entity type **Company Custom Prices**, and upload a CSV file.

Required CSV columns: `company_id`, `sku`, `qty`, `discount_type`, `amount`.

A sample file is available at `Files/Sample/pricescompany_item.csv`:

```
company_id,sku,qty,discount_type,amount
1,24-MB01,1,percentage,15
1,24-MB01,10,percentage,25
2,24-MB01,1,fixed_price,25.99
2,24-MB04,1,fixed_amount,5
```

Supported behaviors: **Add/Update** (append), **Replace**, **Delete**.

Validation checks:

- `company_id` must exist in the `mycompany` table.
- `sku` must exist in the product catalog.
- `discount_type` must be one of `fixed_price`, `fixed_amount`, `percentage`.
- `company_id`, `sku`, `qty`, and `amount` are all required.

---

Developer Guide
---------------

[](#developer-guide)

### Module Structure

[](#module-structure)

```
PricesCompany/
├── Api/
│   ├── Data/
│   │   ├── PricesCompanyItemInterface.php          # Data object contract
│   │   └── PricesCompanyItemSearchResultsInterface.php
│   ├── PricesCompanyItemManagementInterface.php    # Bulk ops + price calculation
│   └── PricesCompanyItemRepositoryInterface.php    # CRUD repository contract
├── Block/Adminhtml/Company/Edit/
│   ├── BackButton.php
│   └── GenericButton.php
├── Controller/Adminhtml/
│   ├── Company/
│   │   ├── Index.php     # Company listing grid
│   │   ├── Edit.php      # Company edit (item grid) page
│   │   └── Save.php      # Placeholder; actual saves happen via AJAX
│   └── Item/
│       ├── Add.php        # AJAX: save products from the pricing dialog
│       ├── Delete.php     # Delete single item
│       ├── GetProducts.php # AJAX: fetch product details for dialog pre-fill
│       ├── InlineEdit.php  # AJAX: inline grid edits
│       └── MassDelete.php  # Mass delete action
├── Files/Sample/
│   └── pricescompany_item.csv    # Sample import file
├── Model/
│   ├── Calculator/
│   │   └── PricesCompanyCalculator.php  # PriceCalculatorInterface impl — injected into CalculatorPool
│   ├── Config/Source/
│   │   ├── DiscountType.php      # Option source for discount_type column
│   │   └── ResolutionMode.php    # Option source for resolution_mode config
│   ├── Import/
│   │   ├── PricesCompanyItem.php           # ImportExport entity class
│   │   └── PricesCompanyItem/Validator.php
│   ├── Item/DataProvider.php
│   ├── Company/DataProvider.php
│   ├── Config.php                     # Typed access to system config values
│   ├── PricesCompanyItem.php          # Model (implements PricesCompanyItemInterface)
│   ├── PricesCompanyItemManagement.php # Management service (bulk ops, calculate)
│   └── PricesCompanyItemRepository.php
├── ResourceModel/PricesCompanyItem/
│   ├── Collection.php
│   └── Grid/Collection.php
├── Ui/Component/Listing/Column/
│   ├── CompanyActions.php
│   └── PricesCompanyItemActions.php
├── view/adminhtml/
│   ├── layout/
│   │   ├── pricescompany_company_edit.xml
│   │   └── pricescompany_company_index.xml
│   ├── ui_component/
│   │   ├── pricescompany_company_form.xml    # Company edit form (wraps item grid)
│   │   ├── pricescompany_company_listing.xml # Company index grid
│   │   └── pricescompany_item_listing.xml    # Item grid inside company edit
│   └── web/js/
│       └── add-products.js    # Product selector + pricing dialog + AJAX save
└── etc/
    ├── acl.xml
    ├── adminhtml/
    │   ├── menu.xml
    │   ├── routes.xml
    │   └── system.xml
    ├── config.xml
    ├── db_schema.xml
    ├── di.xml
    ├── import.xml
    ├── module.xml
    └── webapi.xml

```

### Key Classes

[](#key-classes)

#### Service Contracts

[](#service-contracts)

InterfaceImplementationDescription`Api\Data\PricesCompanyItemInterface``Model\PricesCompanyItem`Data object for a single pricing rule`Api\PricesCompanyItemRepositoryInterface``Model\PricesCompanyItemRepository`CRUD: `getById`, `save`, `delete`, `deleteById`, `getList``Api\PricesCompanyItemManagementInterface``Model\PricesCompanyItemManagement``calculateFinalPrice`, `saveBulk`, `deleteByCompanyId`, `deleteBySku`#### Calculator

[](#calculator)

`Model\Calculator\PricesCompanyCalculator` implements `Orangecat\Prices\Api\PriceCalculatorInterface`:

```
public function calculate(string $sku, float $qty, int $companyId, float $basePrice = 0.0): ?float
```

Selects the highest-matching quantity rule (`qty  float, 'price' => float], ...]` for storefront display.

#### Config

[](#config)

`Model\Config` provides typed access to the two system settings:

```
public function isEnabled($storeId = null): bool
public function getResolutionMode($storeId = null): string  // 'overwrite' | 'stack'
```

### Observers

[](#observers)

This module registers no observers.

### Plugins

[](#plugins)

This module registers no plugins.

### JS Components (Admin)

[](#js-components-admin)

FileExtendsPurpose`view/adminhtml/web/js/add-products.js``Magento_Ui/js/form/components/button`Orchestrates the two-step product selection and pricing dialog flow. On click: reads selected product IDs from the UI component selections provider, fetches product details via AJAX (`GetProducts`), renders a pricing dialog with per-row discount configuration, then POSTs the result to `Item/Add`. On success, reloads the item listing.### Email Templates

[](#email-templates)

This module sends no email notifications.

### ACL Resources

[](#acl-resources)

Resource IDTitleLocation in ACL tree`Orangecat_PricesCompany::manage`Manage Company PricesAdmin &gt; Catalog### Adding Custom Logic

[](#adding-custom-logic)

- **New discount strategy**: Add a constant to `Model\Config\Source\DiscountType`, add the label to `toOptionArray()`, and add a `case` branch in `PricesCompanyCalculator::calculate()` and `::getTiers()`.
- **Custom calculator priority**: Adjust the `di.xml` `CalculatorPool` injection order or weight to control when this calculator runs relative to other pool members (e.g., `PricesList`).
- **Post-save hook**: Add an observer for `Magento\Framework\App\ResourceConnection`-level events, or use a plugin on `PricesCompanyItemRepository::save()` to trigger downstream logic (cache invalidation, ERP sync) after a rule is saved.

---

REST API
--------

[](#rest-api)

All endpoints except `/calculate` require the ACL resource `Orangecat_PricesCompany::manage`. The `/calculate` endpoint is `anonymous` — apply token-based access control at the integration level if needed.

### Endpoints

[](#endpoints)

MethodEndpointDescription`GET``/V1/pricescompany/items/search`Search pricing rules using standard `SearchCriteria``GET``/V1/pricescompany/calculate`Calculate the final price for a company/SKU/qty combination`POST``/V1/pricescompany/items/bulk`Bulk upsert pricing rules (uses `INSERT ON DUPLICATE KEY UPDATE`)`PUT``/V1/pricescompany/items/bulk`Same as POST bulk — idempotent upsert`DELETE``/V1/pricescompany/items/company/:companyId`Delete all pricing rules for a company`DELETE``/V1/pricescompany/items/sku/:sku`Delete all pricing rules for a SKU across all companies### Authentication

[](#authentication)

Generate an integration token with the `Orangecat_PricesCompany::manage` ACL resource, then pass it as a Bearer token.

### Examples

[](#examples)

**Search rules for company 1:**

```
curl -X GET \
  'https://b2bsdk.test/rest/V1/pricescompany/items/search?searchCriteria[filterGroups][0][filters][0][field]=company_id&searchCriteria[filterGroups][0][filters][0][value]=1&searchCriteria[filterGroups][0][filters][0][conditionType]=eq' \
  -H 'Authorization: Bearer '
```

**Calculate final price:**

```
curl -X GET \
  'https://b2bsdk.test/rest/V1/pricescompany/calculate?companyId=1&sku=24-MB01&qty=10' \
  -H 'Authorization: Bearer '
```

Response: `float` — the resolved final price (falls back to catalog base price if no rule matches).

**Bulk upsert:**

```
curl -X POST \
  'https://b2bsdk.test/rest/V1/pricescompany/items/bulk' \
  -H 'Authorization: Bearer ' \
  -H 'Content-Type: application/json' \
  -d '{
    "items": [
      {
        "company_id": 1,
        "sku": "24-MB01",
        "qty": 1,
        "discount_type": "percentage",
        "amount": 15
      },
      {
        "company_id": 1,
        "sku": "24-MB01",
        "qty": 10,
        "discount_type": "percentage",
        "amount": 25
      }
    ]
  }'
```

Response: `true` on success.

**Delete all rules for company 2:**

```
curl -X DELETE \
  'https://b2bsdk.test/rest/V1/pricescompany/items/company/2' \
  -H 'Authorization: Bearer '
```

**Delete all rules for a SKU:**

```
curl -X DELETE \
  'https://b2bsdk.test/rest/V1/pricescompany/items/sku/24-MB01' \
  -H 'Authorization: Bearer '
```

---

Frontend Routes Reference
-------------------------

[](#frontend-routes-reference)

This module has no frontend routes. All user-facing functionality (price display, tier prices) is delivered transparently through the catalog pricing layer managed by `Orangecat_Prices`.

---

DevOps &amp; Integrator Notes
-----------------------------

[](#devops--integrator-notes)

### Deployment Checklist

[](#deployment-checklist)

```
# Enable module
bin/magento module:enable Orangecat_PricesCompany

# Apply schema and DI
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f

# Flush cache
bin/magento cache:flush
```

### Integration Token Scope

[](#integration-token-scope)

Minimum ACL permission required for API access: `Orangecat_PricesCompany::manage` (under `Magento_Catalog::catalog`). The `/calculate` endpoint is `anonymous` and does not require a token, but should be protected at the API gateway level in production if price data is sensitive.

### Disabling Without Uninstalling

[](#disabling-without-uninstalling)

```
bin/magento module:disable Orangecat_PricesCompany
bin/magento setup:upgrade
bin/magento cache:flush
```

When disabled, the `PricesCompanyCalculator` is still registered in the DI graph but `Config::isEnabled()` returns `false`, causing `calculate()` and `getTiers()` to return early without touching the database. No pricing rules are lost.

### Data Integrity Notes

[](#data-integrity-notes)

- **Cascade delete**: Deleting a company from `mycompany` automatically removes all its pricing rules from `pricescompany_item` via the `ON DELETE CASCADE` foreign key. There is no soft-delete — removals are permanent.
- **Unique constraint**: `(company_id, sku, qty)` is enforced at the database level. The bulk upsert uses `INSERT ON DUPLICATE KEY UPDATE`, so re-importing the same combination updates the existing rule rather than creating a duplicate.
- **SKU integrity**: The module does not enforce a foreign key to the catalog. Deleting a product does not automatically remove its pricing rules. Orphan rules (rules referencing deleted SKUs) are harmless but should be cleaned up manually or via the `DELETE /V1/pricescompany/items/sku/:sku` endpoint.
- **Module data persists after disable**: The `pricescompany_item` table and its data survive `module:disable`. To fully remove all data, run `setup:uninstall` or drop the table manually after disabling.

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance99

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity33

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

2

Last Release

2d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/959440?v=4)[Oliverio Gombert](/maintainers/olivertar)[@olivertar](https://github.com/olivertar)

---

Top Contributors

[![olivertar](https://avatars.githubusercontent.com/u/959440?v=4)](https://github.com/olivertar "olivertar (11 commits)")

---

Tags

pricesb2bmagento2companyorangecat

### Embed Badge

![Health badge](/badges/orangecat-module-prices-company/health.svg)

```
[![Health](https://phpackages.com/badges/orangecat-module-prices-company/health.svg)](https://phpackages.com/packages/orangecat-module-prices-company)
```

###  Alternatives

[mollie/magento2

Mollie Payment Module for Magento 2

1131.8M12](/packages/mollie-magento2)[run-as-root/magento2-prometheus-exporter

Magento2 Prometheus Exporter

68353.9k](/packages/run-as-root-magento2-prometheus-exporter)[myparcelnl/magento

A Magento 2 module that creates MyParcel labels

1859.0k](/packages/myparcelnl-magento)[sbodak/magento2-b2b-disable-customer-registration

This extension allows you to disable customer registration in your Magento 2.

1515.7k](/packages/sbodak-magento2-b2b-disable-customer-registration)[mage-os/module-admin-activity-log

The Admin Activity extension makes it easy to track all admin activity with comprehensive audit logging.

293.3k](/packages/mage-os-module-admin-activity-log)[opengento/module-category-import-export

This module add the capability to import and export the categories from the back-office.

1310.2k1](/packages/opengento-module-category-import-export)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
