Download the PHP package justinholtweb/craft-forklift without Composer

On this page you can find all versions of the php package justinholtweb/craft-forklift. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package craft-forklift

Forklift

B2B and wholesale for Craft Commerce 5. Company accounts with buyers and roles, spend approvals, contract price lists, request-a-quote with pay-by-link, net terms and credit limits, quick order and CSV upload, and tax exemption certificates.

WooCommerce has B2BKing, Wholesale Suite and WooCommerce B2B. Craft has Commerce Bulk Pricing and core catalog pricing rules — and nothing that covers the rest. Craft's customer base skews harder to manufacturers and distributors than Woo's does, so this is both the biggest gap in the ecosystem and the best fit for it.

Requires Craft CMS 5.3+, Craft Commerce 5.3+, PHP 8.2+.


What it does

Company accounts, buyers and roles

A Company is a Craft element, so it gets a field layout of your own — rep, region, delivery instructions, whatever your accounts actually differ by — plus search, relations, the trash and an element index with custom sources.

People are attached to a company as members, and the role is a property of the membership, not the person: the same buyer can be an administrator of one account and a viewer on another, which is what happens when a buying group and one of its members both trade with you.

Four roles, deliberately not a permission matrix:

Role Can
Administrator Runs the account. Adds and removes buyers, sets their limits, approves anything, sees the statement.
Approver Approves other people's orders. Does not administer the account.
Buyer Places orders, up to their spend limit. The default.
Viewer Sees the company's orders and quotes and can reorder into a basket, but cannot place an order.

A spend limit is per order and belongs to the member, so "Rita can spend £20,000 and Sam £500" needs no new role. Blank means no limit of their own; zero means they cannot place an order unassisted.

An account can be on hold — which stops new orders and nothing else. Existing orders, quotes, invoices and statements are untouched. That is what a credit controller does at four o'clock on a Friday, and undoes on Monday.

Contract price lists

Craft's catalog pricing rules price by condition. What they cannot express is the thing wholesale actually runs on: a negotiated sheet of prices belonging to one named customer, with quantity breaks, that somebody imported from a spreadsheet and will export back to one.

So a price list is a document. It has a name, an owner (some companies, or every company), dates it is good between, and a body of entries. Four targets, from most specific to least — this variant, this product, this product type, everything — and three ways to say a price:

minQty turns any of them into a quantity break. Several entries on the same product with different quantities are a break table.

Where two lists both price something, the higher priority wins — but only for the lines it actually mentions. A customer contract at priority 10 does not stop a trade-wide list at 0 pricing everything else, which is exactly how "everyone gets 15% off, Acme gets a sheet of negotiated lines" is meant to behave.

Two safety rails, both settings, both defaulting to the safe direction:

Spend approvals

Three ways for an order to need signing off: the company's threshold, the buyer's own spend limit, or a flag on the buyer. The reason is recorded when the request is raised and never recomputed — your threshold can change the next morning, and an audit trail that changes with the settings is not an audit trail.

Approvers decide from a link in an email without signing in, because the people who sign off spend in a purchasing department very often have no login at all, and a workflow that requires one is a workflow that gets bypassed by forwarding the email.

The basket stays live between the request and the decision, so an approval records the amount it covered. An approval for £400 does not authorise a £4,400 basket; a basket that has shrunk needs no re-approval.

Net terms, credit limits and statements

A Purchase Order gateway that extends Commerce's own Manual gateway and authorises rather than purchases — so the order completes, the customer gets their confirmation, and totalPaid stays at zero, which is true, because nobody has paid yet. Capture the transaction when the cheque arrives and Commerce marks it paid with its own machinery. Every report, paid-status filter and integration in your store already understands that.

The gateway offers itself only when the account is set up for terms, is not on hold, and would not go past its credit limit. Being over the limit withdraws the gateway, not the checkout — a customer holding out a credit card should be allowed to use it.

The ledger is append-only and signed: a charge is positive, a payment negative, and the balance is a SUM and nothing else. No running total on the company row, because a cached balance is a number that can be wrong and the moment it goes wrong is the moment somebody is on the telephone.

Statements age the account into the buckets every accounts-receivable ledger uses, allocating payments oldest-charge-first. Nothing about the allocation is stored, so a backdated payment re-ages the account correctly instead of leaving fossilised allocations behind.

Request a quote, then pay by link

This is craftcms/commerce#1156 — "merchant edits an order, customer pays a link" — which Commerce has open and no plugin closes.

  1. The buyer asks. Their basket is snapshotted onto a Quote element, priced as they saw it.
  2. A merchant opens it, changes quantities and prices, adds lines, adds delivery, sets an expiry.
  3. Send. Forklift builds a real, incomplete Commerce cart carrying exactly those prices, and the buyer gets a signed link that loads it into their own session.
  4. From that click it is an ordinary Commerce checkout — every gateway you have, including paying on terms.

The prices survive because Commerce recalculates that cart constantly and every recalculation re-derives the quoted numbers rather than reverting them.

Quick order, CSV upload and reorder

A wholesale buyer does not browse. They have a list of part numbers on a sheet of paper, in a spreadsheet, or in last month's order.

All three routes produce the same result object, so a SKU that cannot be found reads identically however it arrived — and nothing is ever silently dropped. Every row comes back carrying the text the buyer typed and, when it failed, a sentence saying why and the line number it was on. A pad that quietly adds a hundred and eighty-three of two hundred lines is worse than one that refuses the file, because the buyer finds out at the loading bay.

Reordering re-prices at today's rates rather than copying the old numbers, and says which lines changed. A contract that has been renegotiated should not be quietly ignored.

Tax exemption certificates

Certificates are held per company, with a jurisdiction and an expiry, and tax is only removed for one that is approved by a human, unexpired, and covers the delivery address. A Texas resale certificate does not exempt a delivery to Ohio.

The mechanism is a subclass of Commerce's own tax adjuster, not a replacement: rates that add tax are dropped, and rates whose tax is included in the price are transformed so that Commerce's own out-of-zone branch strips them. Every line of tax arithmetic stays Commerce's, so a fix there reaches exempt orders the same day it reaches everybody else's.


Installing

Five default payment terms are created on install (Due on receipt, Net 14, Net 30, Net 60, 2/10 Net 30). Nothing else changes until you create a company.


Templating

Everything a front end needs is on craft.forklift, and nothing on it writes — a template asks questions, a controller makes changes.

The checkout button asks the same verdict the gate reads, so what it says is what pressing it does:

The full surface is in docs/templating.md.

The portal is yours

Every portal screen renders _forklift/portal/<name>.twig from your templates if it exists, and falls back to a plain one Forklift ships. A B2B portal has to look like the shop it is part of.


Console

doctor is the one to reach for first. Every check in it is something that produces no error and no log line but silently stops Forklift doing what you think it is doing: an account with an approval threshold and nobody able to approve, a price list whose SKUs do not exist in your store, an order that completed without the approval it needed because a licence had lapsed.


Editions and downgrades

A lapsed licence never breaks a store. Pro configuration that survives a downgrade is ignored, not obeyed, and three of those choices are worth stating because they are not symmetric:


Architecture, briefly

Two invariants, and everything else is arrangement:

  1. services\Pricing::resolve() is the only place a B2B price is computed. The cart, the catalog templates, the quick-order pad, the CSV upload, the reorder screen, the quote builder and the control panel's price preview all read the PriceResult it returns, so a price a buyer is shown cannot disagree with the price they are charged.
  2. services\Checkout::verdict() is the only place an order's B2B eligibility is decided. The front-end button, the checkout gate, the gateway's availability check and the control panel all read the same verdict, so what the button says is what checkout does.

Six hooks into Commerce and no more: LineItems::EVENT_POPULATE_LINE_ITEM, Payments::EVENT_BEFORE_PROCESS_PAYMENT (the checkout gate — before any money moves), Order::EVENT_BEFORE_COMPLETE_ORDER (the same gate, for orders completed without a payment), Order::EVENT_AFTER_COMPLETE_ORDER, Taxes::EVENT_REGISTER_TAX_ENGINE and Gateways::EVENT_REGISTER_GATEWAY_TYPES. The tax engine is only swapped when Commerce's own is in place — a store running Avalara or TaxJar keeps theirs.

And Forklift's own events, for a module that needs to react or veto: approvals requested and decided, quotes sent, accepted and declined, credit-ledger entries saved and deleted (the before… ones cancelable), and Pricing::EVENT_DEFINE_PRICE to change any price Forklift resolves. See the Events page in the docs.


Testing

Two suites, both run inside the plugin's own test site:

The second exists because two of the bugs found building this were unreachable from a console script and would have shipped: a service calling a method that had never been written, and a controller pushing a string into a Response::setContent() that does not exist. A suite that only calls services keeps missing that class of mistake.

Both are idempotent and self-cleaning.


Documentation

Licence

The Craft License. See LICENSE.md. Lite is free; Pro is licensed per production install through the Craft Plugin Store.


All versions of craft-forklift with dependencies

PHP Build Version
Package Version
Requires php Version ^8.2
ext-json Version *
craftcms/cms Version ^5.3.0
craftcms/commerce Version ^5.3.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package justinholtweb/craft-forklift contains the following files

Loading the files please wait ...