Sales-Man API reference
The Sales-Man persona is the API behind the Orascom sales-agent SPA (https://salesman.orascom.robustastudio.com). It is the widest persona in the backend: 120 routes that let an agent work leads, build pipelines, qualify customers, close sales, submit EOIs for launches, run Taskeen (hand-over) lists, and read sales analytics. Almost everything here is a thin proxy over the CRM middleware — the controllers validate, attach the agent identity, forward to crms-middleware, then map the response back through the standard envelope.
Route file: backend/routes/api/sales-man.php. Controller namespace: App\Http\Controllers\Api\SalesMan (RouteServiceProvider.php:157-160).
Base URL and prefix
Section titled “Base URL and prefix”All routes are registered under the api/sales-man prefix with the api middleware group (RouteServiceProvider.php:157-160).
| Environment | Base URL |
|---|---|
| Staging | https://api.orascom.robustastudio.com/api/sales-man |
| Local | http://localhost/api/sales-man |
Example: GET https://api.orascom.robustastudio.com/api/sales-man/leads.
Every response is wrapped by FormatApiResponse into { "data": ..., "message": ... }, except the two routes marked withoutMiddleware([FormatApiResponse]) (the signed PDF download and the broken inline-PDF route). The throttle:api limiter allows 60 requests/min per token, raised to 180/min for any path containing /analytics (RouteServiceProvider.php:131-136).
Authentication — Microsoft SSO to Sanctum
Section titled “Authentication — Microsoft SSO to Sanctum”Sales agents do not log in with a password. They authenticate through Microsoft Azure SSO (Laravel Socialite azure driver), which mints a Sanctum personal-access token carrying the access-sales-man-api ability.
The two SSO entry points live in routes/web.php (namespace App\Http\Controllers), not in the persona API file:
| Method | Path | Controller | Purpose |
|---|---|---|---|
| GET | login/microsoft |
SalesMan\Auth\MicrosoftController@microsoftLogin |
Redirect to Microsoft (MicrosoftController.php:18) |
| GET | login/microsoft/callback |
SalesMan\Auth\MicrosoftController@handleProviderCallback |
Handle callback, mint token, redirect to SPA (MicrosoftController.php:23) |
On callback the controller reads the Azure profile (email, name, microsoft_id), upserts the agent via SaveUserInfo, then issues the token with the sales-man ability and redirects the browser to SALESMAN_FRONTEND_URL/?token=<token> (MicrosoftController.php:52-57). The SPA stores that token and sends it as Authorization: Bearer <token> on every subsequent API call.
Inside sales-man.php, the authenticated block stacks auth:sanctum + ability:access-sales-man-api (sales-man.php:20). The user-provider:sales_man middleware rebinds the web guard to the SalesMan model (table sales_team) for the whole file (sales-man.php:8).
Three auth tiers exist in this file:
- Public (no token) —
user-provider:sales_manonly. The 5 sale-info lookup filters plus the signed PDF download. - Authenticated — adds
auth:sanctum+ability:access-sales-man-api. Units, destinations, saved units, notifications, sales-offer PDFs. - Authenticated + country-scoped — additionally requires the
countryheader (see below). Leads, pipelines, customers, sales, meetings, EOIs, Taskeen, CRM lookups, analytics.
The country header requirement
Section titled “The country header requirement”ValidateSalesManCountry reads the country header and resolves a published Country by slug, then binds it into the container as a scoped Country instance that the country-scoped controllers type-hint (ValidateSalesManCountry.php:27-37):
$country = Country::published() ->where('slug', $request->header('country'))->first();
if (! $country) { throw new FailedToGetCountry();}- Header name:
country. - Accepted values are published country slugs:
egypt,oman,montenegro(CountryEnums). - Missing/unknown/unpublished value →
FailedToGetCountryis thrown before the controller runs.
The header does more than gate access — it selects per-country validation rules and date handling. create_lead, create_pipeline, create_sale, disqualify_lead, submit_meeting, update_pipeline, mark_pipeline_as_lost, and create_unit_type each dispatch to an Egypt / Oman / Montenegro ValidatorInterface implementation via CountryRequestValidator::make(...). So the accepted request body for the same endpoint differs by the country header value.
Which groups need it:
| Route group | Needs country header |
|---|---|
| Auth (logout) | No |
| Public sale-info filters + signed PDF | No |
| Units, filters, saved units, sales-offer PDF | No |
| Destinations | No |
| User / notifications | No |
| Leads | Yes |
| Pipelines | Yes |
| Customers (qualify) | Yes |
| Sales | Yes |
| Meetings | Yes |
| EOI & launches | Yes |
| Taskeen | Yes |
| CRM lookups / conditional fields | Yes |
| Analytics | Yes |
In the tables below, {sid} = the authenticated agent’s CRM source id, and {api} = CRMS_MIDDLEWARE_API_URL.
Base: user-provider:sales_man + auth:sanctum + ability:access-sales-man-api. No country header.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | logout |
Revoke the current access token | Authed. Broken — namespace/method mismatch, see Gotchas |
Public sale-info filters and signed PDF
Section titled “Public sale-info filters and signed PDF”Base: user-provider:sales_man only. No token, no country header.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | sales-nationalities |
List sale nationalities | Public · CRM GET {api}/nationalities |
| GET | sales-countries |
List sale countries | Public · CRM GET {api}/countries |
| GET | sales-cities |
List sale cities | Public · CRM GET {api}/cities |
| GET | sales-occupations |
List sale occupations | Public · CRM GET {api}/occupations |
| GET | sales-connections |
List sale connections | Public · CRM GET {api}/connections |
| GET | units/{publishedUnitSalesMan}/sales-offer/{salesOfferPdfGeneration}/download |
Stream a generated sales-offer PDF | Public but signed URL · raw response (no envelope) |
Units, filters and saved units
Section titled “Units, filters and saved units”Base: authed, no country header. {publishedUnitSalesMan} resolves to a sales-man-published Unit; {publishedAvailableUnitSalesMan} additionally requires the unit to be available (both 404 out-of-scope ids before the controller runs).
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | filters |
Available unit filters | Authed |
| GET | units |
List/filter units | Authed |
| GET | units/export |
Export units to xlsx | Authed |
| GET | units/compare |
Compare units by ids | Authed |
| GET | units/{publishedAvailableUnitSalesMan} |
Show unit details | Authed · binding (published + available) |
| GET | units/{publishedUnitSalesMan}/payment-terms |
Fetch unit payment terms | Authed · CRM GET {api}/units/{source_id}/payment-terms |
| POST | units/{publishedUnitSalesMan}/{saleSourceId}/reservation-form |
Download reservation-form PDF | Authed · CRM POST {api}/sales/{saleSourceId}/reservation-form |
| POST | units/{publishedUnitSalesMan}/share-reservation-link |
Email a reservation link to a lead | Authed · local (mail) |
| GET | user/saved-units |
List saved units | Authed |
| POST | user/saved-units/{publishedAvailableUnitSalesMan} |
Save a unit | Authed · salesman-resource-visibility:publishedAvailableUnitSalesMan |
| DELETE | user/saved-units/{savedUnit} |
Remove a saved unit | Authed · salesman-resource-visibility:savedUnit |
Sales-offer PDF
Section titled “Sales-offer PDF”Base: authed, no country header. Generation is async — POST .../generate dispatches GenerateSalesOfferPdfJob to the PDF_SERVICE_URL microservice; poll .../status, then download via the signed URL above.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | units/{publishedUnitSalesMan}/sales-offer |
Download the CRM sales-offer PDF | Authed · CRM GET {api}/units/{source_id}/sales-offer |
| GET | units/{publishedUnitSalesMan}/sales-offer/{paymentPlanId} |
(legacy inline PDF) | Broken — target method missing, see Gotchas |
| POST | units/{publishedUnitSalesMan}/sales-offer/{paymentPlanId}/generate |
Start async PDF generation | Authed · throttle:sales-offer-generate (default 5/min) |
| GET | units/{publishedUnitSalesMan}/sales-offer/{salesOfferPdfGeneration}/status |
Poll generation status + file URL | Authed |
Destinations
Section titled “Destinations”Base: authed, no country header. {destinationSlug} resolves to a published Destination.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | destinations |
List assigned published destinations | Authed |
| GET | destinations/{destinationSlug} |
Show destination details | Authed |
| GET | destinations/{destinationSlug}/projects |
List allowed projects | Authed |
| GET | destinations/{destinationSlug}/projects/{project}/marketing-materials |
Download project marketing zip | Authed · builds zip |
| GET | destinations/{destinationSlug}/unit-types |
List destination unit types | Authed |
| GET | destinations/{destinationSlug}/locations |
List destination locations | Authed |
| GET | destinations/{destinationSlug}/testimonials |
List destination testimonials | Authed |
| GET | destinations/{destinationSlug}/facilities |
List destination facilities | Authed |
| GET | destinations/{destinationSlug}/faqs |
List destination FAQs | Authed |
| GET | destinations/{destinationSlug}/construction-updates |
List construction updates | Authed |
| GET | destinations/{destinationSlug}/educational-hubs |
List educational hubs | Authed |
Leads and pipelines
Section titled “Leads and pipelines”Base: authed + country-scoped (country header required).
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | leads |
List leads | Country · CRM GET {api}/salesman/{sid}/leads |
| GET | leads/search |
Search leads | Country · CRM GET {api}/salesman/{sid}/leads/search |
| POST | leads/check-lead-eligible |
Check reservation eligibility, return a reservation link | Country · CRM POST {api}/salesman/{sid}/leads/check-lead-eligible |
| GET | leads/{leadSourceId} |
Show lead details | Country · CRM GET {api}/salesman/{sid}/leads/{leadSourceId} |
| POST | leads |
Create a lead | Country · CRM POST {api}/salesman/{sid}/leads |
| POST | leads/{leadSourceId}/disqualify |
Disqualify a lead | Country · CRM POST {api}/salesman/{sid}/leads/{leadSourceId}/disqualify |
| GET | leads/{leadSourceId}/feedback |
List lead feedback | Country · CRM GET {api}/salesman/{sid}/leads/{leadSourceId}/feedback |
| GET | pipelines |
List pipelines | Country · CRM GET {api}/salesman/{sid}/pipelines |
| POST | pipelines |
Create a pipeline | Country · CRM POST {api}/salesman/{sid}/pipelines |
| PATCH | pipelines/{pipelineSourceId} |
Update a pipeline | Country · CRM PATCH {api}/salesman/{sid}/pipelines/{pipelineSourceId} |
| PATCH | pipelines/{pipelineSourceId}/lost |
Mark a pipeline as lost | Country · CRM PATCH {api}/salesman/{sid}/pipelines/{pipelineSourceId}/lost |
| GET | pipelines/{pipelineSourceId}/unit-types |
List pipeline unit types | Country |
| POST | pipelines/{pipelineSourceId}/unit-types |
Add a pipeline unit type | Country |
| GET | pipelines/{pipelineSourceId}/feedback |
List pipeline feedback | Country |
| GET | pipelines/{pipelineSourceId}/units |
List pipeline units | Country |
Qualify to customers
Section titled “Qualify to customers”Base: authed + country-scoped. Qualifying a lead means creating the CRM customer record it graduates into.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | customers |
List customers | Country · CRM GET {api}/salesman/{sid}/customers |
| GET | customers/search |
Search customers | Country · CRM GET {api}/salesman/{sid}/customers/search |
| GET | customers/{customerSourceId} |
Show customer details | Country · CRM GET {api}/salesman/{sid}/customers/{customerSourceId} |
| POST | customers |
Create (qualify) a customer from a lead | Country · CRM POST {api}/salesman/{sid}/customers |
| POST | customers/{customerSourceId}/attach |
Attach a file to a customer | Country · CRM POST {api}/salesman/{sid}/customers/{customerSourceId}/attach |
Base: authed + country-scoped. {publishedUnitSalesMan} binding gates the unit.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | sales |
List the agent’s sales | Country · CRM GET {api}/salesman/{sid}/sales |
| GET | sales/search |
Search sales | Country · CRM GET {api}/salesman/{sid}/sales/search |
| POST | units/{publishedUnitSalesMan}/sales |
Create a sale | Country · CRM POST {api}/salesman/{sid}/sales |
| POST | units/{publishedUnitSalesMan}/sales/{saleSourceId}/attach |
Attach a file to a sale | Country · CRM POST {api}/salesman/{sid}/sales/{saleSourceId}/attach |
| PATCH | units/{publishedUnitSalesMan}/sales/{saleSourceId} |
Update a sale | Country · CRM PATCH {api}/salesman/{sid}/sales/{saleSourceId} |
Meetings
Section titled “Meetings”Base: authed + country-scoped.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | meetings |
List the agent’s meetings | Country · CRM GET {api}/salesman/{sid}/meetings |
| GET | pipelines/{pipelineSourceId}/meetings |
List meetings for a pipeline | Country |
| POST | pipelines/{pipelineSourceId}/meetings |
Submit a meeting on a pipeline | Country · per-country validation |
CRM lookups and conditional fields
Section titled “CRM lookups and conditional fields”Base: authed + country-scoped. These back the conditional dropdowns in the lead/pipeline/sale forms; each proxies the matching CRM list.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | ambassadors |
List ambassadors | Country · CRM GET {api}/salesman/{sid}/ambassadors |
| GET | events |
List events | Country · CRM GET {api}/salesman/{sid}/events |
| GET | offices |
List offices | Country · CRM GET {api}/salesman/{sid}/offices |
| GET | system-users |
List system users | Country · CRM GET {api}/salesman/{sid}/system-users |
| GET | exhibitions |
List exhibitions | Country · CRM GET {api}/salesman/{sid}/exhibitions |
| GET | crm-units |
List CRM units | Country · CRM GET {api}/salesman/{sid}/units |
| GET | brokers |
List brokers | Country · CRM GET {api}/salesman/{sid}/brokers |
| GET | sales-agents |
List sales agents | Country · CRM GET {api}/salesman/{sid}/sales-agents |
| GET | competitions |
List competitions | Country · CRM GET {api}/salesman/{sid}/competitions |
| GET | corporates |
List corporates | Country · CRM GET {api}/salesman/{sid}/corporates |
| GET | lead-sources |
List lead sources | Country · CRM GET {api}/salesman/{sid}/lead-sources |
| GET | lead-sources/{sourceId}/subsources |
List lead sub-sources | Country |
| GET | lead-countries |
List lead countries | Country · CRM GET {api}/salesman/{sid}/lead-countries |
Launches, EOI and bank accounts
Section titled “Launches, EOI and bank accounts”Base: authed + country-scoped. {publishedLaunch} resolves to a published Launch; {launchValue} resolves a published launch by slug or id. Full-EOI submission (storeAll) is async — it stores uploads to disk and dispatches SaveEOIToCrmJob.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | launches |
List the agent’s launches | Country · local |
| GET | launches/{launchValue} |
Show launch details | Country · local |
| GET | launches/{launchValue}/facilities |
List launch facilities | Country · local |
| GET | launches/{launchValue}/faqs |
List launch FAQs | Country · local |
| GET | bank-accounts |
List bank accounts | Country · local |
| GET | eois |
List EOIs | Country · CRM GET {api}/eois |
| GET | eois/search |
Search EOIs | Country · CRM GET {api}/eois/search |
| GET | eois/export |
Export filtered EOIs | Country · CRM GET {api}/eois |
| POST | launches/{publishedLaunch}/eois |
Submit a full EOI (async) | Country · dispatches SaveEOIToCrmJob |
| PATCH | launches/{publishedLaunch}/eois/{eoiBySourceId} |
Update an EOI + notify | Country · CRM PATCH {api}/launches/{source_id}/eois/{eoiSourceId} |
| PATCH | launches/{publishedLaunch}/eois/{eoiSourceId}/require-action |
Set EOI require-action | Country |
| POST | launches/{publishedLaunch}/eois/{eoiSourceId}/attach |
Attach a file to an EOI | Country |
| PATCH | launches/{publishedLaunch}/eois/{eoiSourceId}/cancel |
Cancel an EOI | Country |
| GET | launches/{publishedLaunch}/eois/{eoiSourceId} |
Show EOI details | Country |
| GET | launches/{publishedLaunch}/eois/{eoiSourceId}/export |
Export a single EOI as PDF | Country |
| GET | launches/{publishedLaunch}/eois/{eoiSourceId}/attachments |
List EOI attachments | Country |
| DELETE | launches/{publishedLaunch}/eois/{eoiSourceId}/attachments/{attachmentSourceId} |
Delete an EOI attachment | Country |
| GET | launches/{publishedLaunch}/unit_types/{priceSourceId}/preferences |
Fetch launch unit-type preferences | Country |
| GET | launches/{publishedLaunch}/eois/{eoiBySourceId}/resend-payment-link |
Resend the EOI payment invitation | Country · local (mail) |
| POST | launches/{publishedLaunch}/eois/{eoiSourceId}/products |
Create an EOI product | Country |
| PATCH | launches/{publishedLaunch}/eois/{eoiSourceId}/products/{productSourceId} |
Update an EOI product | Country |
| PATCH | launches/{publishedLaunch}/eois/{eoiBySourceId}/products/{productSourceId}/delete |
Delete a product, sync payment | Country |
| POST | launches/{publishedLaunch}/eois/{eoiSourceId}/products/{productSourceId}/unit-types |
Create a product unit type | Country |
| PATCH | launches/{publishedLaunch}/eois/{eoiBySourceId}/products/{productId}/unit-types/{unitTypeId}/delete |
Delete a unit type, sync payment | Country |
| POST | launches/{publishedLaunch}/eois/{eoiSourceId}/products/{productSourceId}/unit-types/{unitTypeSourceId}/preferences |
Create a unit-type preference | Country |
| DELETE | launches/{publishedLaunch}/eois/{eoiSourceId}/products/{productSourceId}/unit-types/{unitTypeSourceId}/preferences/{prefId} |
Delete a preference | Country |
| POST | launches/{publishedLaunch}/eois/{eoiBySourceId}/products/full-product |
Create a full product tree | Country · composite |
| POST | launches/{publishedLaunch}/eois/{eoiBySourceId}/products/{productSourceId}/unit-types/store-bulk |
Bulk-create product unit types | Country · composite |
Taskeen
Section titled “Taskeen”Base: authed + country-scoped. Taskeen = the unit hand-over list.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | taskeen |
List taskeen records | Country · CRM GET {api}/taskeen |
| GET | taskeen/export |
Export taskeen to xlsx/csv | Country · CRM GET {api}/taskeen |
Analytics
Section titled “Analytics”Base: authed + country-scoped. All are local (query the mirrored DB, not the CRM). Paths under /analytics get the raised 180/min rate limit.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | analytics/filters/destinations |
Destinations filter | Country · local |
| GET | analytics/filters/unit-types |
Unit-types filter | Country · local |
| GET | analytics/filters/sales-team |
Sales-team filter | Country · local |
| GET | analytics/sales/overview-summary |
Sales overview summary | Country · local |
| GET | analytics/sales/deals-vs-closed-deals-per-month |
Deals vs closed deals per month | Country · local |
| GET | analytics/sales/sale-sources-breakdown |
Sales by sale-source | Country · local |
| GET | analytics/sales/pipeline-types-breakdown |
Sales by pipeline-type | Country · local |
| GET | analytics/sales/unit-categories-breakdown |
Sales by unit-category | Country · local |
| GET | analytics/sales/sum-sales-revenues-daily-breakdown |
Daily sales-revenue breakdown | Country · local |
| GET | analytics/leads/categories-breakdown |
Leads by category | Country · local |
| GET | analytics/agents-ranking |
Sales-agent ranking | Country · local |
| GET | analytics/meetings/overview-summary |
Meetings overview summary | Country · local |
| GET | analytics/meetings/meetings-with-no-feedback |
Meetings lacking feedback | Country · local |
| GET | analytics/meetings/meetings-count-per-salesman |
Meetings count per salesman | Country · local |
User and notifications
Section titled “User and notifications”Base: authed, no country header.
| Method | Path | Purpose | Auth / notes |
|---|---|---|---|
| GET | user/notifications |
List the agent’s notifications | Authed · local |
| POST | user/notifications/read |
Mark notifications read | Authed · local |
Request / response detail for key endpoints
Section titled “Request / response detail for key endpoints”The five write endpoints below drive the core sales workflow. All require the country header; the request body for create lead, create pipeline, and create sale is the union of common rules plus a per-country rule set selected by CountryRequestValidator (the Egypt variant is shown).
POST leads — create a lead
Section titled “POST leads — create a lead”LeadController@store (LeadController.php:60), validated by SubmitLeadRequest (SubmitLeadRequest.php:19).
Common body (SubmitLeadRequest.php:26):
| Field | Rule |
|---|---|
first_name |
required, string |
last_name |
required, string |
phone |
required, string |
phone_2 … phone_4 |
nullable, string |
email |
nullable, string |
destination_slug |
required, must exist in destinations |
preferred_communication_method |
nullable, CommunicationMethod enum |
preferred_communication_time |
nullable, CommunicationTime enum |
customer_inquiry |
nullable, CustomInquiryEnums |
interested_in_unit_id |
nullable, must exist in units |
budget |
nullable, numeric, min 0 |
portal_comments |
nullable, string |
Egypt-specific additions (Country/Egypt/CreateLeadRequest.php:13): channel_id (required, ChannelEnums), feedback_id (EgyptLeadFeedbackEnums), lead_quality_code (LeadQualityEnums), plus nullable source ids ambassador_source_id, broker_source_id, salesagent_source_id, office_source_id, corporate_source_id, event_source_id, exhibition_source_id, and referral fields.
Behavior (LeadController.php:77-105): forwards to POST {api}/salesman/{sid}/leads. On CRM 200 it also records a local UserRequest and returns the mapped CRM body. On non-200 it extracts the CRM message; if it contains "duplicate" it fires the DuplicateLead notification to the agent and returns 400 with that message.
POST pipelines — create a (lead-discovery) pipeline
Section titled “POST pipelines — create a (lead-discovery) pipeline”PipelinesController@store (PipelinesController.php:56), validated by CreatePipelineRequest (CreatePipelineRequest.php:15) which delegates to the per-country validator.
Egypt body (Country/Egypt/CreatePipelineRequest.php:16, extends PipelineFeedbackRequest):
| Field | Rule |
|---|---|
lead_source_id |
nullable, required without customer_source_id, string |
customer_source_id |
nullable, required without lead_source_id, string |
pipeline_type |
required, LeadPipelineTypeEnums (1 = single unit, 2 = multiple units) |
destination_slug |
required, must exist in destinations |
plus feedback fields inherited from PipelineFeedbackRequest |
— |
A pipeline must be anchored to either a lead or a customer (mutually-required pair). Forwards to POST {api}/salesman/{sid}/pipelines; returns the mapped body on 200, else 400 with the CRM message (PipelinesController.php:83-90).
POST customers — qualify (create) a customer
Section titled “POST customers — qualify (create) a customer”CustomersController@store (CustomersController.php:78), validated by CreateCustomerRequest (CreateCustomerRequest.php:11).
| Field | Rule |
|---|---|
lead_source_id |
required, string |
country_id |
required, string |
address |
required, string |
marital_status |
required, int |
nationality_id, origin_nationality_id |
nullable, string |
city_id / city_name, district_id / district_name, other_district_name |
nullable |
occupation_code |
nullable, int |
occupation_name, other_occupation_name |
nullable, string |
national_id_number, passport_number |
nullable, string |
expiry_date, issue_date, date_of_birth |
nullable, int (timestamps) |
gender |
nullable, GenderEnums |
email, issued_from, arabic_issue_from, arabic_address |
nullable, string |
Forwards to POST {api}/salesman/{sid}/customers; returns the mapped body on 200, else 400 with the CRM message (CustomersController.php:104-112).
POST units/{publishedUnitSalesMan}/sales — create a sale
Section titled “POST units/{publishedUnitSalesMan}/sales — create a sale”SalesController@store (SalesController.php:56), validated by CreateSaleRequest (Sale/CreateSaleRequest.php:10) delegating to the per-country validator.
Egypt body (Country/Egypt/CreateSaleRequest.php:12):
| Field | Rule |
|---|---|
customer_source_id |
required, string |
pipeline_source_id |
required, string |
paymentplan_source_id |
required, string |
reservation_amount |
required, numeric |
reservation_payment_method |
required, PaymentMethods enum |
selling_price |
required, numeric |
currency |
nullable, CurrencyEnums |
currency_rate |
nullable, decimal (0–3 places) |
Forwards to POST {api}/salesman/{sid}/sales. A sale ties together an existing customer, pipeline, and payment plan.
POST launches/{publishedLaunch}/eois — submit a full EOI (async)
Section titled “POST launches/{publishedLaunch}/eois — submit a full EOI (async)”EOIController@storeAll (EOIController.php:382), validated by SubmitFullEOIRequest (SubmitFullEOIRequest.php:18).
Key fields:
| Field | Rule |
|---|---|
name, phone, email |
required, string |
phone_2, phone_3, comment |
nullable |
paid_amount, currency_id |
required, int |
payment_status |
required, EOIPaymentStatusEnums |
payment_method |
required, EOIPaymentMethodEnums |
attachment_1 |
required file (1–5120 KB) |
attachment_2, cheque_attachment |
nullable file |
customer_identification_number_type |
nullable, EOICustomerIdentificationNumberTypeEnums |
instapay_attachment[] |
nullable array of { attachment (file), referenceid (string) } |
products[] |
present array; each item quantity (int) + unittypes[] |
products.*.unittypes.*.unit_type_id |
required, must exist in unit_types |
products.*.unittypes.*.project_id |
required, must exist in projects |
products.*.unittypes.*.preferences.*.preference_source_id |
required, string |
products.*.unittypes.*.preferences.*.rank |
required, integer |
Behavior (EOIController.php:385-431): sets source_of_sale = DIRECT, persists every uploaded file to the eoi-documents disk, mints a unique EOI-<uuid> id, and dispatches SaveEOIToCrmJob. It returns respondSuccess() immediately — the CRM write happens on the queue, so a 200 here means “accepted”, not “created in CRM”.
POST leads/check-lead-eligible — eligibility gate + reservation link
Section titled “POST leads/check-lead-eligible — eligibility gate + reservation link”LeadController@checkLeadEligible (LeadController.php:211), validated by CheckLeadEligibleRequest. Forwards to POST {api}/salesman/{sid}/leads/check-lead-eligible. If the CRM returns is_eligible: true, the controller generates a reservation link via GenerateUnitReservationLinkAction::handleForSalesMan(...) and returns { "reservation_link": "..." }. Otherwise it returns 400 with “This lead is not eligible for a new reservation because an active reservation already exists.” (LeadController.php:226-236).
Gotchas
Section titled “Gotchas”The country header is silent-but-fatal. Any of the ~85 country-scoped routes (sales-man.php:88-269) returns a FailedToGetCountry error when the country header is missing, empty, or set to a slug that is not a published country (ValidateSalesManCountry.php:27-37). There is no default fallback. The accepted values are egypt, oman, and montenegro. Beyond gating access, the header also selects the country-specific validation rules for create/update lead, pipeline, sale, meeting, and disqualify — so a request body that validates for egypt may be rejected for oman/montenegro.
