Developer notes
Data model
Section titled “Data model”| Table | Contents |
|---|---|
?:kv_mao_offers |
One row per offer: company (MV vendor of the product, ULT storefront company), storefront, product snapshot (name, code, options as JSON, options text), quantity, list_price (cart unit price at offer time, primary currency), offer_price, counter_price, final_price, customer (user id or e-mail and name), status, rounds, respond_until, accept_until, access_key (secret of the signed link), order id, auto (A/D), consent time, dates. |
?:kv_mao_messages |
History: author (C customer, S staff, X store), action (offer, counter, accept, decline, withdraw, expire, order, reopen), price, message. |
?:kv_mao_rules |
Rules per object (P product, C category, V vendor): mode (‘’/Y/N), min_price (products), accept_pct, floor_pct (NULL = inherit). |
Statuses
Section titled “Statuses”P waiting for the merchant · C counter-offer (waiting for the customer) · A accepted · O ordered · D declined · W withdrawn or counter declined by the customer · E expired.
Every change is a conditional UPDATE ... WHERE status IN (...), so two people answering at the same time cannot both win.
Price enforcement
Section titled “Price enforcement”- An offer line carries only
extra.kv_mao = {offer_id}. OnlyCartBridge::addOfferToCart()creates it;pre_add_to_cartremoves markers sent by the browser and keeps the server-side marker on cart updates.generate_cart_idgives the line its own cart id. exclude_products_from_calculationruns on every calculation before the core reads prices: the visitor must own the offer (same account; guest offers: a session grant from the signed link or the submission), statusA,accept_untilnot passed, same product and options. Valid lines getstored_price = Y, the agreed price and exactly the offered quantity; anything else is removed.get_cart_product_data_postremoves catalog promotions from offer lines (unless the policy is “All promotions”);calculate_cart_itemssets$apply_cart_promotions = falsewhile an offer line is in the cart (policy “Offer price only”).pre_place_order: the quantity must equal the offer quantity, thenUPDATE ... SET status = 'O' WHERE status = 'A' AND accept_until >= now(single use; all offers of the cart or none).place_orderlinks the order (Multi-Vendor: the vendor’s child order).change_order_statusto F, D or I anddelete_ordergive the offer back while its deadline has not passed.- The list price is computed with
fn_get_cart_product_data()on a temporary cart (quantity discounts, option modifiers, catalog promotions, stock check), never taken from the request.
Hooks used
Section titled “Hooks used”gather_additional_products_data_post (button flag on the product page and quick view), pre_add_to_cart, generate_cart_id, exclude_products_from_calculation, calculate_cart_items, get_cart_product_data_post, pre_place_order, place_order, change_order_status, delete_order, update_product_post, update_category_post, clone_product, delete_product_post, delete_category_post, delete_company, post_delete_user. Signatures are identical in 4.17.2 and 4.21.2.
Template hooks: buttons:add_to_cart (post), checkout:product_options (post, cart badge), profiles:my_account_menu (post), index:scripts / index:styles; admin products:detailed_content, categories:detailed_content.
Own hook: kv_make_an_offer_notify_pre($event, &$data, &$rules) before every notification.
Full page cache
Section titled “Full page cache”The product page prints only a static button (it depends on the product, the rules and the settings, never on the visitor; the products.view main block cache gets the rules table and the licence state as keys). The dialog body, the offer pages and all actions are AJAX or POST requests to kv_make_an_offer.*, which is not a cacheable dispatch. Storefront requests write only to the add-on’s tables.
Controllers
Section titled “Controllers”| Area | Dispatch | |
|---|---|---|
| Storefront | kv_make_an_offer.form |
POST (AJAX): dialog body for the posted product form; GET: page version |
kv_make_an_offer.submit |
POST: create an offer | |
kv_make_an_offer.manage, .view |
My offers, offer page (offer_id, key) |
|
.accept_counter, .decline_counter, .new_offer, .withdraw, .add_to_cart |
POST actions (offer_id, key) |
|
| Admin / vendor | kv_make_an_offer.manage, .update, .export, .vendor_rules |
List, offer page, CSV, vendor defaults |
.accept, .counter, .decline, .delete, .m_delete |
POST actions (delete: administrators only) | |
| Console | admin.php --dispatch=kv_make_an_offer.cron |
Expires overdue offers and sends the e-mails |
Storefront POSTs are CSRF-protected (schemas/security/csrf_validation.post.php); the form also has a honeypot field.
File map
Section titled “File map”app/addons/kv_make_an_offer/ addon.xml init.php func.php controllers/backend/kv_make_an_offer.php, products.post.php, categories.post.php, kv_make_an_offer_license.php controllers/frontend/kv_make_an_offer.php schemas/block_manager/blocks.post.php products.view cache keys schemas/menu/menu.post.php, menu_vendor.post.php, actions.post.php schemas/notifications/events.post.php, events_multivendor.post.php schemas/permissions/admin.post.php, vendor_multivendor.post.php schemas/security/csrf_validation.post.php Tygh/Addons/KvMakeAnOffer/ Access.php ownership, signed links, session grants CartBridge.php cart lines, price enforcement, single use Config.php settings, statuses Notifier.php events OfferDataProvider.php e-mail data OfferService.php offer life cycle, limits, automatic answers, expiry Pricing.php options, list price, amounts Repository.php offers, history, search, statistics Rules.php rule resolution License/ Upgrade/ license client, self-healing upgradesdesign/backend/templates/addons/kv_make_an_offer/ admin pages, rule fields, hooksvar/themes_repository/responsive/templates/addons/kv_make_an_offer/ button, dialog body, My offers, offer page, cart badgejs/addons/kv_make_an_offer/func.js dialogvar/langs/en/addons/kv_make_an_offer.po