Freight ERP user manual

Freight ERP user manual

This manual describes the screens as they behave in this build. Each chapter names the sidebar link, the fields, the buttons, and the messages the application actually shows. Pictures were taken from the synthetic Nile Freight demo. A picture that was not taken is named in the chapter, with the reason, and again in the screenshot index.

How to find something: open the HTML copy and use the search box, or use the contents. Procedures are at the end of each chapter. Words used on the screens are in the glossary. The first screen for each role is in the quick start.

Counts from the coverage file, after the chapters were written:

Item Described Of
Screens 119 120
Tabs 19 19
Forms, including filters and uploads 295 299
Fields 550 551
Confirmation dialogs 119 120
Actions 219 219
Downloads and exports 15 15
Reports and their CSV exports 14 14
Roles 24 24
Walkthroughs W01–W13 13 13
Scheduler and recovery functions 12 12
Acceptance scenarios 74 78

What is not described as a working screen is the old two-factor page and its controls. That page is no longer part of the application, and the gaps report names it as a specification disagreement. The four acceptance scenarios that are not described are LIVE03–LIVE06, which wait for a live mail, tax, payment or hosting test. They were not run, and they are not marked passed.

828 manual test cases are in the workbook. All of them are Not run.

1. Getting started: sign-in, navigation, dashboard and search

This chapter explains how to sign in, how to find your way around the staff application, what the dashboard shows and how search works. It also lists, for every default role, which parts of the application the role unlocks. Every internal user needs it: Platform administrator, Security administrator, Company administrator, Branch manager, Sales representative, Sales manager, Pricing officer, Pricing manager, Operations coordinator, Operations manager, Documentation officer, Customs coordinator, Warehouse or dispatch user, AR accountant, AP accountant, Treasury user, Finance manager, Executive and Auditor. Customer and agent portal users (Customer viewer, Customer collaborator, Customer organization administrator, Agent viewer, Agent collaborator) use the same sign-in page but get the portal shell, which is described in the portal chapters.

Permission keys used in this chapter: dashboard.finance.view (financial position on the dashboard), operations.task.view, operations.alert.view, crm.enquiry.list, operations.shipment.view, operations.job.view, partners.partner.list (dashboard sections and search), system.health.view (health link). Every sidebar link is shown by a permission prefix (see The staff shell). The key dashboard.view is seeded on every internal role, but the code does not check it: any signed-in internal user can open the dashboard (see the gaps file).

References: Spec §19.1 (user lifecycle, MFA, reset), §19.2 (permission model: "UI menus only reflect access; they do not enforce it"), §19.3 (default roles), §20 (screens), §25 (security), §33.4 (portal roles). UML 02 (identity and authentication), UML 03 (scoped authorization).

How errors look. Most refusals in this application appear on one of two pages. When you open a page you may not see (HTTP 403) or a record that does not exist in the active company (HTTP 404), the message appears inside the normal application layout, in your language. When a form you submit is refused (any POST), a plain page shows the status number, a heading, the exact refusal message, a Return to the start page button and a "Reference" code. That plain page is always in English. Use the browser's Back button to return to your form. Quote the Reference code when you report a problem.

Sign in

How to open it: go to /login (staff) or /portal/login (portal users; same form). Any page you open without a valid session sends you here. The page uses the sign-in layout: a short pitch on the left ("Freight operations, finance and customer service in one place.", "Authorised users only. Activity is recorded.") and the form on the right. The left side is hidden on screens narrower than 1200 px. Arabic: Sign in (تسجيل الدخول).

Sign-in page in English

The page shows the heading Sign in, the text "Use your work email and password.", the form, a Forgot password? link and a language button (العربية / English) at the top.

Sign-in page in Arabic, right to left

Fields

Field Required What to enter / rules
email (shown as Email) Yes Your work email address. The browser checks the email format.
password (shown as Password) Yes Your password. It is never shown or stored in logs.

Actions

Button/link What it does Permission Confirmation Result
Sign in Posts to /login None (public) None A correct password opens /dashboard. Refused: the form is shown again with the message.
Forgot password? Opens /password/forgot None None Reset request form.
العربية / English Opens /locale?lang=ar or /locale?lang=en None None The page reloads in the chosen language.

Rules and messages

  • Wrong email or password, or an inactive account: "The email or password is not valid." (HTTP 401). The message does not say which part was wrong.
  • After 5 failed attempts for the same email from the same IP address within 15 minutes: "Too many sign-in attempts. Try again later." (HTTP 429). The lock lifts when the 15-minute window passes. Each refused attempt is recorded in the audit log as a failed sign-in.
  • The form carries a hidden form token. If the page was open too long or the token is missing, the post is refused with HTTP 419 "The form token is not valid. Reload the page and try again." on the plain error page, heading "Form expired".
  • A successful sign-in records the audit event auth.login.

Role differences

None on this page. After sign-in, internal users get the staff shell; external (portal) users get the portal shell. A correct password opens the dashboard for every role. There is no code page.

Forgot password

How to open it: Forgot password? on the sign-in page, or /password/forgot. Arabic: Reset password (إعادة تعيين كلمة المرور).

Forgot password form

The page shows "Reset password", the text "Enter the email address of your account.", the form and a Back to sign in link.

Fields

Field Required What to enter / rules
Email Yes The email address of your account.

Actions

Button/link What it does Permission Confirmation Result
Request reset Posts to /password/forgot None (public) None Always shows the same notice, whether or not the account exists. If the account exists, a reset token valid for 30 minutes is created and the audit event auth.reset_requested is recorded.
Back to sign in Opens /login None None Sign-in page.

Rules and messages

After submitting, the page always reads: "If the account exists, a reset token was created. Reset emails are not sent by this installation yet, so the token is not sent and is not shown here. Ask an administrator to reset your password."

Known defect D8: reset emails are not sent. The token is created but no email goes out, so a user cannot finish a reset on their own. Ask an administrator.

Role differences

None.

Reset password

How to open it: /password/reset with a reset token in the link. Because reset emails are not sent (D8), a user normally never receives this link. Arabic: Choose a new password (اختر كلمة مرور جديدة).

Reset password form

Fields

Field Required What to enter / rules
password (shown as New password) Yes At least 12 characters, with at least one letter and one number. The reset token travels in a hidden field token.

Actions

Button/link What it does Permission Confirmation Result
Update password Posts to /password/reset A valid reset token None Success: you go to /login. All your existing sessions are ended, and the audit event auth.password_reset is recorded. Refused: the form is shown again with the message.

Rules and messages

  • "Use at least 12 characters with a letter and a number."
  • "This reset link is not valid." (wrong, used or expired token; tokens expire after 30 minutes and work once).

Role differences

None.

The staff shell: sidebar, header and session

How to open it: every staff page after sign-in uses this shell (layouts/app). This is the staff application shell. Arabic labels for the sidebar groups: Workspace (مساحة العمل), Sales (المبيعات), Operations (العمليات), Finance (المالية), Master data (البيانات الرئيسية), Administration (الإدارة).

Staff shell with numbered parts

Numbered parts in the annotated shot: 1 company switcher, 2 sidebar navigation, 3 header search box, 4 language button, 5 alerts bell, 6 user menu.

The sidebar lists only the links you can use. A link is shown when you hold at least one allowed permission whose key starts with one of the prefixes below. Hiding a link never grants or removes access: the page itself checks permissions again, and a direct URL to a page you may not see returns "Not allowed". A group with no visible link is not shown.

Group Link label Route Shown when you hold a permission starting with
Workspace Dashboard /dashboard always shown
Workspace My queue /activities crm.activity
Workspace Tasks /tasks operations.task
Workspace Alerts (with open count) /alerts operations.alert
Workspace Approvals /approvals operations. (any operations permission)
Sales Enquiries and leads /enquiries crm.enquiry.list
Sales Customers /customers crm.customer
Sales Quotations /quotations pricing.quotation
Sales Rates and tariffs /tariffs pricing.buy_rate, pricing.sell_rate, pricing.margin
Sales Commissions /commissions sales.commission
Sales Booking intake /intake bookings.intake
Sales Bookings /bookings bookings.booking
Operations Jobs and shipments /jobs operations.job, operations.shipment
Operations Consolidations /consolidations operations.consolidation
Operations Road trips /trips operations.trip
Operations Customs /customs operations.customs
Operations Cargo custody /custody operations.custody
Operations Documents /documents documents.file
Finance Finance overview /finance finance.journal, dashboard.finance
Finance Invoices /invoices finance.invoice
Finance Receipts /receipts finance.receipt
Finance Supplier bills /supplier-bills finance.supplier_bill
Finance Supplier payments /supplier-payments finance.supplier_payment
Finance Bank reconciliation /bank finance.bank
Finance Reports /reports finance.report
Finance Intercompany /intercompany finance.intercompany
Master data Partners /partners partners.partner.list
Master data Imports /imports data.import
Master data Reference data /reference reference.
Administration Organization /organization organization.
Administration Users and roles /admin/users identity.user, system.role
Administration Customer portal /admin/portal portal.organization, portal.shipment.grant, portal.content, portal.document.publish
Administration Portal enquiries /admin/portal/enquiries portal.enquiry.manage
Administration Portal uploads /admin/portal/uploads portal.upload.review
Administration Integrations /integrations system.integration
Administration API credentials /admin/api-credentials system.api
Administration System health /admin/health system.health
Administration Audit log /admin/audit system.audit
Administration Backups /admin/backups system.backup
Administration Upgrade /admin/upgrade system.backup

Below the links the sidebar footer reads "Times shown in {tz}" (your display time zone). The page footer reads "Figures come from recorded data only."

  • Open navigation (فتح قائمة التنقل) button: shown only on narrow screens (below 992 px). It slides the sidebar in.
  • Search box with the placeholder "Search jobs, shipments, customers…". Press Enter to open /search. Below 576 px the box is hidden and a search icon links to /search instead.
  • Company name badge: shown on wide screens (992 px and up).
  • Language button: shows العربية in English and English in Arabic. It opens /locale?lang=ar or /locale?lang=en.
  • Alerts bell: shown only if you hold operations.alert.view. The red number counts open alerts that are assigned to you or not assigned to anyone, capped at 99.
  • User menu: your initials and name. It shows "Signed in as" with your name and a Sign out (تسجيل الخروج) button. There are no other entries (no profile or password change page for staff).

User menu opened

Sidebar of a Sales representative

Session timeout

Your session ends after 30 minutes without activity (setting security.session.idle_timeout, default 1800 seconds) and in any case 8 hours after sign-in (default 28800 seconds). Every page you open extends the idle time. Two minutes before the idle limit, a banner says "Your session will expire soon because of inactivity. Save your work." At the limit it says "Your session has expired. Sign in again to continue." The next page you open sends you to /login. A session also ends when an administrator revokes it, when your account is deactivated, or when your password is reset.

Fields

Field Required What to enter / rules
company_id (shown as Company) Yes Choose one of the companies you are a member of. The list changes the company as soon as you pick one; without JavaScript, use the Switch company button. The selector appears only when you have at least one company membership.
q (shown as the search box Search) Yes Words to search for. Submitted with GET to /search.

Actions

Button/link What it does Permission Confirmation Result
Company selector / Switch company Posts to /context/company An active membership in the chosen company None The active company changes, the active branch is cleared and you go to /dashboard.
Search box (Enter) Opens /search?q=… Session None Search results page.
Language button Opens /locale?lang=en or /locale?lang=ar None None The language is stored for your session. You return to the page you were on (same site only); otherwise to /dashboard.
Alerts bell Opens /alerts operations.alert.view None Alerts list (chapter 2).
Sign out Posts to /logout Session None The session is ended, the audit event auth.logout is recorded and you go to /login.
Shared one-button actions Many screens use a one-button form (components/action_form) that posts to the record's action route with the form token As for the action Shown when the action is irreversible As for the action.

Rules and messages

  • Choosing a company you are not a member of: HTTP 403 "That company is not in your memberships."
  • If you have no active company, the first active membership is used when you sign in.
  • Any form posted with a missing or old form token: HTTP 419 "The form token is not valid. Reload the page and try again."
  • While a backup or restore is running, every form post is refused with the plain text "Writes are paused while a backup runs. Try again in a few minutes." (HTTP 503). Pages can still be read. Sign out is also blocked during that time (known defect D19).

Role differences

The sidebar content depends entirely on your roles; see Roles and what each role sees. The alerts bell is hidden without operations.alert.view.

Mobile and off-canvas navigation

On screens narrower than 992 px the sidebar is hidden off the edge of the screen. Tap Open navigation to slide it in; a dark backdrop covers the page. Tap the backdrop or press Escape to close it. Below 576 px the search box is replaced by a search icon and text labels next to header icons are hidden.

Mobile navigation opened

Arabic and right-to-left layout

Choose العربية in the header (or on the sign-in page). The whole page switches to right-to-left: the sidebar moves to the right, the right-to-left Bootstrap stylesheet is loaded and the off-canvas sidebar slides in from the right. Labels and messages come from the Arabic catalogue. Codes, references and numbers stay left-to-right inside the text. Two exceptions stay in English: the plain error page for refused form posts, and messages that have no Arabic translation (for example the heading "Form expired").

Dashboard in Arabic

Staff dashboard

How to open it: sidebar Workspace > Dashboard, or /dashboard (also /). Arabic: Dashboard (لوحة المعلومات).

Dashboard for a user who sees almost everything

The subtitle reads "Recorded work and figures for the active company. Sections follow your permissions." Each section appears only if you hold the permission shown below. If you have no active company, the page shows "No active company" and "Choose a company in the sidebar to see its work and figures."

Section Shown when you hold What it shows
KPI My open tasks (مهامي المفتوحة) operations.task.view Open tasks for you or your teams; note "{n} overdue" or "None overdue".
KPI Open alerts (التنبيهات المفتوحة) operations.alert.view Open alerts for you or unassigned; note "{n} critical" or "Assigned to you or unassigned".
KPI Approvals waiting (موافقات بالانتظار) every internal user Approval requests you may decide; note "Requests you can decide".
KPI Open enquiries (الاستفسارات المفتوحة) crm.enquiry.list Count of open enquiries.
KPI Active shipments (الشحنات النشطة) operations.shipment.view Shipments "Not completed or cancelled".
KPI Partner profiles (ملفات الشركاء) partners.partner.list Count of partner profiles.
Financial position (المركز المالي) dashboard.finance.view Customer receivable, Supplier payable, Open carrier accruals, Recognized gross profit. "Posted ledger balances in the company base currency ({currency}). Drafts are excluded."
Recent jobs (أحدث الملفات) operations.job.view Last 8 jobs, link All jobs.
Recent partners partners.partner.list Last 5 partners, link All partners.
My next tasks (مهامي التالية) operations.task.view Your next tasks, link Open queue. Empty: "No open tasks are assigned to you or your teams."
Open alerts list operations.alert.view Link All alerts. Empty: "No open alerts."
Waiting for your decision (بانتظار قرارك) every internal user, only when not empty Link Open approvals.
Open system health system.health.view Link to /admin/health.

Fields

The dashboard has no forms.

Actions

Button/link What it does Permission Confirmation Result
All jobs, All partners, Open queue, All alerts, Open approvals, Open system health Open the related list As for the target page None The list page.

Rules and messages

Figures are read from recorded data only. Financial figures are posted balances; drafts are not included.

Role differences

A Sales representative sees only Approvals waiting, Open enquiries and Partner profiles. An AR accountant sees Approvals waiting and Financial position. A Platform administrator sees Approvals waiting and the Open system health link. See the role table below.

Dashboard for a Sales representative

How to open it: type in the header search box and press Enter, or open /search?q=…. Arabic: Search (بحث).

Search results

The page title is Search with the subtitle "Partners in the active company that match your search." It has one panel, Partners (الشركاء), with a count and the columns Code and Legal name. If nothing matches: "No partners matched this search in the active company."

Search covers partners only, although the header placeholder mentions jobs, shipments and customers. The search page itself has no search box: you search from the header. On phones (below 576 px) the header box is hidden, so you cannot type a new search there (see the gaps file).

Fields

Field Required What to enter / rules
q (header search box Search) Yes Part of a partner code or legal name.

Actions

Button/link What it does Permission Confirmation Result
Header search (Enter) GET /search partners.partner.list for results None Matching partners. Without partners.partner.list the page opens with an empty result and no refusal.

Rules and messages

Results come only from the active company.

Search for a user without partner list rights: always empty

Role differences

Users without partners.partner.list (for example Pricing officer, AR accountant, Executive, Auditor) always get the empty result.

Error and message pages

How they appear: automatically, when a request is refused. This is the error/message page. Arabic: Not allowed (غير مسموح), Not found (غير موجود).

Not allowed page inside the layout

Situation Status What you see
You open a page your roles do not allow (GET) 403 Inside the layout: Not allowed, "Your roles do not include this page. Ask an administrator if you need access." and Back to dashboard.
You open a record that does not exist in the active company (GET) 404 Inside the layout: Not found, "That page does not exist or is not available in the active company."
You open an address that does not exist 404 Inside the layout: Not found, "That page does not exist."
A form post is refused, or the application cannot start 403, 404, 409, 422, 419, 503, 500 Plain page (English only, fallbackErrorPage): the status number; heading Not allowed (403), Not found (404), Form expired (419), Temporarily unavailable (503), Something went wrong (500 and above), or The request could not be completed (other codes such as 409 and 422); the refusal message; Return to the start page; "Reference {code}".
A form post during a backup 503 Plain text only: "Writes are paused while a backup runs. Try again in a few minutes." (D19)
A program or browser asking for JSON any A JSON body with error, message and correlation_id.

Not found page for a record outside the active company

The page Module unavailable opens from /unavailable links: heading Not installed (غير مثبت), "This module is not installed yet. No sample data is shown." and Back to dashboard.

Module unavailable page

Fields

None.

Actions

Button/link What it does Permission Confirmation Result
Back to dashboard Opens /dashboard Session None Dashboard.
Return to the start page Opens / None None Dashboard if signed in, otherwise sign-in.

Rules and messages

The Reference code on the plain page identifies the request in the technical log. Give it to support.

Role differences

Portal users get portal message pages instead: for a staff address they see "Not found" and "That page does not exist or is not shared with you." (HTTP 404).

Installer and portal layouts

Two more layouts exist. The installer layout (/install, title "Freight ERP · Installer") is used only before the application is installed, behind the setup secret; it is described in the administration chapter. The portal shell (/portal/* and the portal /dashboard) is used by external users: links Overview, Shipments, Enquiries, Notifications (with unread count), Invoices (only when billing is shared), Exports, Members (organization administrators), Switch organization (several organizations), Profile, Security, the language link and Sign out, which posts to /logout. The portal is described in the portal chapters.

Portal shell for a customer viewer

Roles and what each role sees

The table lists the default roles as seeded (EssentialSeed::roleCatalog). Roles are editable templates, so your installation may differ. A person may hold several roles; the sidebar then shows the union. No role asks for a second sign-in code.

ID Role Second sign-in code Sidebar groups and links shown Dashboard sections (besides Approvals waiting) Example page that returns Not allowed
ROL-01 Platform administrator No Workspace: Dashboard. Administration: Organization, Integrations, API credentials, System health, Audit log, Backups, Upgrade. Open system health /customers
ROL-02 Security administrator No Workspace: Dashboard. Administration: Users and roles, Audit log. none /tasks
ROL-03 Company administrator No Workspace: Dashboard, My queue, Tasks, Alerts, Approvals. Sales: all except Commissions. Operations: all. Finance: Finance overview, Invoices, Receipts, Supplier bills. Master data: Partners, Imports, Reference data. Administration: Organization, Customer portal, Portal enquiries, Portal uploads, Integrations, API credentials. All KPIs, Financial position, Recent jobs, Recent partners, tasks and alerts /admin/audit
ROL-04 Branch manager No Workspace: Dashboard, Tasks, Alerts, Approvals. Sales: Bookings. Operations: all. Master data: Partners, Reference data. Administration: Organization. My open tasks, Open alerts, Active shipments, Partner profiles, Recent jobs, Recent partners /enquiries
ROL-05 Sales representative No Workspace: Dashboard, My queue. Sales: all seven links. Master data: Partners, Reference data. Open enquiries, Partner profiles, Recent partners /tasks
ROL-06 Sales manager No Same links as Sales representative. Extra rights on the same screens: customer management and financials, commission management and approval, margin approval, intake token setup. Open enquiries, Partner profiles, Recent partners /tasks
ROL-07 Pricing officer No Workspace: Dashboard. Sales: Enquiries and leads, Quotations, Rates and tariffs. Master data: Reference data. Open enquiries /customers
ROL-08 Pricing manager No As Pricing officer, plus Administration: Audit log. Open enquiries /customers
ROL-09 Operations coordinator No Workspace: Dashboard, Tasks, Alerts, Approvals. Sales: Bookings. Operations: all. Master data: Partners, Reference data. Operations KPIs, Recent jobs, Recent partners, tasks and alerts /enquiries
ROL-10 Operations manager No As Operations coordinator; extra approval rights inside the operations screens. As Operations coordinator /customers
ROL-11 Documentation officer No Workspace: Dashboard, Tasks, Approvals. Operations: Jobs and shipments, Documents. My open tasks, Recent jobs, My next tasks /alerts
ROL-12 Customs coordinator No As Operations coordinator (customs management rights added). As Operations coordinator /enquiries
ROL-13 Warehouse or dispatch user No Workspace: Dashboard, Tasks, Approvals. Operations: Jobs and shipments, Road trips, Cargo custody, Documents. My open tasks, Recent jobs, My next tasks /alerts
ROL-14 AR accountant No Workspace: Dashboard. Sales: Customers. Finance: Finance overview, Invoices, Receipts, Reports. Financial position /enquiries
ROL-15 AP accountant No Workspace: Dashboard. Finance: Finance overview, Supplier bills, Supplier payments, Reports, Intercompany. Master data: Imports. Financial position /customers
ROL-16 Treasury user No Workspace: Dashboard. Finance: Finance overview, Supplier bills, Supplier payments, Bank reconciliation. Financial position /customers
ROL-17 Finance manager No Workspace: Dashboard, Approvals. Sales: Customers, Commissions. Operations: Jobs and shipments, Customs. Finance: all eight links. Master data: Imports. Administration: Audit log. Financial position, Recent jobs /enquiries
ROL-18 Executive No Workspace: Dashboard. Finance: Finance overview, Reports. Administration: Organization. Financial position /tasks
ROL-19 Auditor No Workspace: Dashboard. Finance: Finance overview, Reports. Administration: Organization, Audit log. none /tasks
ROL-20 Customer viewer No Portal shell: Overview, Shipments, Enquiries, Notifications, Profile, Security (no enquiry creation, upload or download rights). Portal overview Staff addresses show the portal "Not found" page (404)
ROL-21 Customer collaborator No Portal shell; can also create enquiries, upload files and download published documents. Portal overview as ROL-20
ROL-22 Customer organization administrator No As Customer collaborator; Members appears when the membership role is administrator. Portal overview as ROL-20
ROL-23 Agent viewer No As Customer viewer, for agent organizations. Portal overview as ROL-20
ROL-24 Agent collaborator No As Customer collaborator but without document download. Portal overview as ROL-20

Notes:

  • The Approvals link is shown to any role with an operations permission, but /approvals and the "Approvals waiting" KPI are open to every internal user. An Executive or AR accountant can open /approvals by URL and sees only requests their permissions allow them to decide.
  • /commissions never returns Not allowed: users without commission rights see "You can read your own statement only." (chapter 3).
  • /organization has no permission check (known defect D12): roles without an organization. permission do not see the link but can open the page by URL.

Procedures

Sign in

  1. Open /login.
  2. Enter your work email in Email and your password in Password.
  3. Select Sign in.
  4. The dashboard opens.

Change language

  1. In the header, select العربية.
  2. The current page reloads in Arabic, right to left.
  3. To go back, select English in the header.

Switch company

  1. In the sidebar, open the Company list.
  2. Choose the company. The page changes to /dashboard for that company.
  3. Check the company name in the header badge (wide screens).

Find a partner

  1. Type part of the partner name or code in the header search box.
  2. Press Enter.
  3. Open the result you need from the Partners panel.

Sign out

  1. Open the user menu (your initials, top right).
  2. Select Sign out. The sign-in page opens.

Request a password reset

  1. On the sign-in page select Forgot password?.
  2. Enter your Email and select Request reset.
  3. Read the notice. Because reset emails are not sent (D8), ask an administrator to reset your password.

Troubleshooting

Symptom Cause What to do
"The email or password is not valid." Wrong email or password, or the account is inactive. Check the email. Ask an administrator if the account may be inactive.
"Too many sign-in attempts. Try again later." Five failed attempts within 15 minutes from your address. Wait 15 minutes, then try again.
"The authentication code is not valid." The code expired or the device clock is wrong. Wait for the next code. Check the phone's time setting.
No reset email arrives Reset emails are not sent (D8). Ask an administrator.
"This reset link is not valid." The token is wrong, already used or older than 30 minutes. Request a new reset through an administrator.
"Form expired" with "The form token is not valid. Reload the page and try again." The page was open too long, or you signed in again in another tab. Go back, reload the page and submit again.
"Not allowed" when opening a page Your roles do not include it. Ask an administrator for the role you need.
"Not found" for a record you expect The record belongs to another company, or does not exist. Switch to the right company in the sidebar.
"That company is not in your memberships." You chose a company you are not a member of. Ask an administrator for a membership.
A sidebar link is missing You hold no permission with the link's prefix. See the sidebar table; ask an administrator.
Search finds nothing for a job or customer Search covers partners only. Use the list pages (Jobs and shipments, Customers).
"Your session has expired. Sign in again to continue." 30 minutes without activity, or 8 hours since sign-in. Sign in again. Save work before long breaks.
"Writes are paused while a backup runs. Try again in a few minutes." A backup or restore is running (D19). Wait a few minutes and try again.

2. My work: activities, tasks, alerts and approvals

The Workspace group collects the work that is waiting for you: sales follow-ups (My queue), operational tasks, alerts raised by the scheduled scan, and approval requests that need a second person. Sales representatives and Sales managers use My queue (crm.activity.manage). Operations coordinators, Operations managers, Customs coordinators, Branch managers, Documentation officers, Warehouse or dispatch users and Company administrators use Tasks (operations.task.view, operations.task.manage). Operations roles, Branch managers and Company administrators use Alerts (operations.alert.view, operations.alert.manage). Anyone who holds a decide permission for an approval type uses Approvals: Operations manager (operations.release.approve, operations.exception.approve, operations.cost_estimate.approve, operations.commitment.approve), Company administrator (operations.cost_estimate.approve) and Finance manager (finance.claim.approve, finance.disbursement.approve).

References: Spec §5.1 and §5.2 (activities), §12 (milestones, tasks, exceptions and approvals), §19.2 (maker-checker). UML 07 (CRM activities), UML 12 (tasks), UML 03 (approvals), UML 38 (queue task lifecycle).

When a form post is refused, a plain English page shows the status, the heading "The request could not be completed" (409 or 422) or "Not allowed" (403), the exact message and a Reference code. Use the browser's Back button to return. Successful posts return to the list without a success message; check the row's new status.

My queue

How to open it: sidebar Workspace > My queue, route /activities. Customer 360 also has a My queue button. Arabic: My queue (قائمة أعمالي).

My queue with the new activity form

The page title is My queue, subtitle "Follow-ups and tasks assigned to you." Three KPIs count your activities: Open (مفتوح), Overdue (متأخر, open with a due date before today, UTC) and Due today (مستحقة اليوم). Selecting a KPI opens that view.

The Queue view tabs (عرض القائمة) are Open (/activities?view=open), Overdue (?view=overdue) and Closed (?view=done, done or cancelled). The table columns are Due, Kind, Subject, Customer, From and Status. Status values: open (to do), done (completed, with the outcome), cancelled. A second list, Assigned by me (أسندتها أنا), shows open activities you created for other people.

The New activity form

Fields

New activity form (posts to /activities):

Field Required What to enter / rules
kind (shown as Kind) Yes Follow-up, Task, Call or Meeting. (Note is offered only on Customer 360.)
subject (shown as Subject) Yes Up to 200 characters.
due_on (shown as Due on) For Follow-up and Task A date. Follow-ups and tasks must have one.
assigned_to (shown as Assign to) No An active internal member of this company. Defaults to you.

Row forms on open activities:

Field Required What to enter / rules
outcome (placeholder Outcome, with Done) No What happened, up to 500 characters.
assigned_to (shown as Reassign to, with Reassign) Yes The new assignee: an active internal member of this company.

Actions

Button/link What it does Permission Confirmation Result
Add Creates the activity (POST /activities) crm.activity.manage None Back on /activities; the activity is open (a note would be created as done). Audit activity.created.
Done Completes the activity (POST /activities/{id}/complete) crm.activity.manage, and you are the assignee or the creator None Status done, outcome saved. Audit activity.completed.
Reassign Gives the activity to another member (POST /activities/{id}/reassign) As for Done None New assignee; status stays open. Audit activity.reassigned.
Cancel (no button) The route POST /activities/{id}/cancel exists and records audit activity.cancelled, but the screen offers no Cancel button. As for Done — —

Rules and messages

  • "Choose a follow-up, task, call, meeting or note."
  • "Subject is required (up to 200 characters)."
  • "Due date must be a date."
  • "A follow-up or task needs a due date."
  • "Notes are limited to 2000 characters."
  • "The outcome is limited to 500 characters."
  • "Activities can only be assigned to active internal members of this company." (422)
  • "Customer was not found." / "The related record was not found." (404, when a customer or record link is wrong)
  • "Activity was not found." (404)
  • "Only the assignee or the person who created this activity can change it." (403)
  • "This activity is already closed." (409)

Role differences

Without crm.activity.manage the sidebar hides My queue and /activities shows "Not allowed". Only the assignee or the creator can complete or reassign; other users get the 403 message above.

Tasks

How to open it: sidebar Workspace > Tasks, route /tasks. The dashboard link Open queue also leads here. Arabic: Tasks (المهام).

Tasks list

Subtitle: "Open tasks from milestones, templates and manual entry. Overdue tasks are highlighted." Tasks come from milestone templates, task templates and manual entry on a job (see the jobs chapter). The Task scope tabs (نطاق المهام) are Mine and my teams (/tasks?scope=mine: tasks you own, plus tasks with no individual owner whose team is one of your teams) and All open (/tasks?scope=all). A header link Templates opens /task-templates (jobs chapter).

The task table partial has the columns Task, Job, Owner or team, Due, Priority (normal or high), Checklist (done/total) and Status. Overdue rows are highlighted. Empty list: "No tasks." Only open tasks are listed.

Fields

The list has no form.

Actions

Button/link What it does Permission Confirmation Result
Mine and my teams / All open Changes the scope operations.task.view None Filtered list.
Task title Opens /tasks/{id} operations.task.view None Task detail.
Templates Opens /task-templates operations.task.view None Template list.

Rules and messages

Without operations.task.view, /tasks shows "Not allowed".

Role differences

Sales, pricing, finance (except where an operations permission is held), Executive and Auditor roles do not see Tasks.

Task detail

How to open it: select a task title in Tasks, or a task on a job; route /tasks/{id}. Arabic: Checklist (قائمة التحقق), Cancel task (إلغاء المهمة).

Task detail with checklist, comments and actions

Numbered parts in the annotated shot: 1 summary, 2 checklist, 3 comments, 4 Complete, 5 Reassign, 6 Cancel task.

Task detail on a phone

The summary shows Job, Owner, Due, Priority and Status. Status values: open, done, cancelled. If the task depends on another task, a notice says "Depends on {task}; it cannot be completed first." A cancelled task shows its cancel notice. Then come the Description, the Checklist (each item is a toggle button; items marked "(optional)" do not block completion) and Comments.

Fields

Field Required What to enter / rules
Add a comment Yes Comment text, up to 2000 characters. Submitted as body.
New owner One of New owner or Team An active internal user of this company, or "No individual owner". Submitted as owner_user_id.
Team One of New owner or Team Team name, up to 64 characters. Submitted as team_name.
Cancel reason Yes Why the task is cancelled, up to 500 characters. Submitted as reason.
Checklist toggle done — Each checklist button submits done=1 (Mark done) or done=0 (Mark not done).

Actions

Button/link What it does Permission Confirmation Result
Checklist item (Mark done / Mark not done) POST /task-items/{id}/toggle operations.task.manage None Item ticked or unticked. Disabled when the task is closed. Audit task.checklist.
Comment POST /tasks/{id}/comment operations.task.manage None Comment added (also on closed tasks). Audit task.commented.
Complete POST /tasks/{id}/complete operations.task.manage None Status done. Audit task.completed.
Reassign POST /tasks/{id}/reassign operations.task.manage None New owner and/or team. Audit task.reassigned.
Cancel (in Cancel task) POST /tasks/{id}/cancel operations.task.manage "Cancel this task?" Status cancelled with the reason. Audit task.cancelled.

The routes above are grouped in the fact pack as /tasks/{id}/(complete|cancel|reassign|comment).

Rules and messages

  • "Only an open task can be completed." (409)
  • "Complete the task this one depends on first." (409)
  • "Tick the required checklist items first." (409)
  • "Only an open task can be cancelled." (409)
  • "Choose an owner or a team." (422)
  • "Only an open task can be reassigned." (409)
  • "The owner must be an active internal user of this company."
  • "Reason is required." / "Reason is too long."
  • "Comment is required." / "Comment is too long."
  • "Team is too long."
  • "Checklist item was not found." (404) / "The task is closed." (409)
  • "Task was not found." (404)

Role differences

The Complete, Reassign, Cancel and Comment controls are shown to anyone who can open the task. Users with operations.task.view but without operations.task.manage (none of the default roles) would get "You do not have permission for this action." when they press them.

Alerts

How to open it: sidebar Workspace > Alerts (with the open count), the header bell, or the dashboard link All alerts; route /alerts. Arabic: Alerts (التنبيهات), Alert status (حالة التنبيهات).

Alerts list

Subtitle: "Alerts are raised by the scheduled scan (overdue tasks and pickups, approvals waiting too long, completed jobs not invoiced, customs holds). Each rule raises at most one alert per entity per window." The scan runs from the scheduled cron task (Developer-assisted to trigger). Rules and severities: task overdue (warning), pickup overdue (critical), approval waiting over 24 hours (warning), completed job not fully billed (warning), customs hold or inspection (critical). An alert still open (not acknowledged) 24 hours after it was raised is escalated once: its severity becomes critical and the row shows "escalated". When a snooze time passes, the next scan sets the alert back to open.

Scope buttons: Mine and unowned (/alerts?scope=mine, default) and All (?scope=all). The Alert status tabs: Active (default: everything not resolved), Open, Acknowledged, Snoozed and Resolved (&status=active|open|acknowledged|snoozed|resolved). Columns: Alert (title and rule key), Severity (and "escalated"), Job, Owner ("Unowned" if none), Raised, Status (with "until …" for snoozed and the resolution note for resolved) and Action.

Alerts in Arabic, right to left

Status meanings: open (new), acknowledged (someone has seen it), snoozed (hidden from attention until the time shown), resolved (closed with a note).

Fields

Field Required What to enter / rules
Snooze until (UTC) Yes A date and time in UTC within the next 14 days. Submitted as until.
note (placeholder Resolution note) Yes What was done.

Actions

Button/link What it does Permission Confirmation Result
Acknowledge POST /alerts/{id}/acknowledge operations.alert.manage None Status acknowledged. Shown only for open alerts.
Snooze POST /alerts/{id}/snooze operations.alert.manage None Status snoozed until the time. Shown for open and acknowledged alerts.
Resolve POST /alerts/{id}/resolve operations.alert.manage None Status resolved with the note. Shown for any unresolved alert.

These are grouped in the fact pack as /alerts/{id}/(acknowledge|snooze|resolve). After each action you return to /alerts.

Rules and messages

  • "Snooze until a time within the next 14 days."
  • "Snooze until must be a UTC date and time."
  • "Resolution note is required."
  • "Alert was not found." (404)
  • "This alert cannot move to {to} from {status}." (409, for example resolving an already resolved alert from an old page)

Role differences

Without operations.alert.view the Alerts link and the header bell are hidden and /alerts shows "Not allowed". The action buttons are not hidden for viewers without operations.alert.manage; the server refuses them.

Approvals

How to open it: sidebar Workspace > Approvals, or the dashboard link Open approvals; route /approvals. The sidebar link is shown only to roles with an operations permission, but the page opens for every internal user. Arabic: Approvals (الموافقات), Waiting for your decision (بانتظار قرارك), Your requests (طلباتك).

Approvals waiting for a decision

Subtitle: "Each request is fixed to the exact facts it was raised for. You cannot approve your own request, and an approved request is used once; if the facts change before it is used, it becomes stale."

Requests are raised from other screens (jobs, consolidations, customs, claims). The types are:

Request label Raised with Decided with Expires after
Release despite holds operations.release.override operations.release.approve 24 hours
Start a leg before its predecessor arrived operations.leg.manage operations.exception.approve 72 hours
Adjust an approved carrier cost operations.consolidation.manage operations.cost_estimate.approve 168 hours
Approve a purchase order operations.commitment.manage operations.commitment.approve 168 hours
Set a claim reserve operations.claim.manage finance.claim.approve 168 hours
Settle a claim operations.claim.manage finance.claim.approve 168 hours
Fund a customs disbursement operations.customs.manage finance.disbursement.approve 168 hours

Waiting for your decision lists pending requests of the types you may decide, raised by someone else. Columns: Request (number, label and amount), Job, Requested by, Reason, Facts (the fixed details), Expires and Decision. Empty: "Nothing is waiting for you."

Your requests lists your own last 50 requests: Request, Job, Status, Decided by, Note and Used. Status values: pending (waiting), approved (may be used once), rejected, expired (not decided in time), consumed (used by the action it authorised). When the facts changed before use, the action refuses with "The situation changed after the approval. Ask for a new approval of the current details." Empty: "No requests."

Fields

Field Required What to enter / rules
note (placeholder Decision note) Yes on screen Up to 500 characters. The screen requires it for both buttons; the server requires it only for Reject.

Actions

Button/link What it does Permission Confirmation Result
Approve POST /approvals/{id}/decide with decision=approve The type's decide permission None Status approved. Audit approval.approved; an outbox event is written.
Reject POST /approvals/{id}/decide with decision=reject The type's decide permission "Reject this request?" Status rejected. Audit approval.rejected.

Rules and messages

  • "Approval request was not found." (404)
  • "This approval action is no longer configured." (409)
  • "Decision note is required." (when rejecting) / "Decision note is too long."
  • "This request is not waiting for a decision." (409)
  • "The requester cannot decide their own request." (409)
  • "This request expired before it was decided. Ask for a new one." (409; the request is marked expired)
  • "You do not have permission for this action." (403, no decide permission for the type)

Role differences

Each user sees only the types they may decide. A Pricing officer, Executive or AR accountant normally sees "Nothing is waiting for you."

Approvals opened by URL by an Executive: nothing to decide Your own requests never appear in your decision list.

Procedures

Add a follow-up for yourself

  1. Open Workspace > My queue.
  2. In New activity, choose Kind Follow-up.
  3. Enter the Subject and a Due on date.
  4. Leave Assign to on your name and select Add.
  5. The activity appears under Open.

Close a follow-up

  1. Open My queue, tab Open.
  2. In the row, type what happened in Outcome.
  3. Select Done. The row moves to Closed.

Complete a task with a checklist

  1. Open Workspace > Tasks and select the task title.
  2. Select each required checklist item to mark it done.
  3. Optionally add a comment in Add a comment and select Comment.
  4. Select Complete. The status becomes done.

Hand a task to a team

  1. Open the task.
  2. In Reassign, choose "No individual owner" in New owner and type the team in Team.
  3. Select Reassign.

Snooze an alert until tomorrow morning

  1. Open Workspace > Alerts.
  2. In the alert row, enter a time in Snooze until (UTC). Remember it is UTC, not your local time.
  3. Select Snooze. The alert moves to the Snoozed tab.

Decide an approval request

  1. Open Workspace > Approvals.
  2. Read Reason and Facts in the row.
  3. Type your Decision note.
  4. Select Approve, or Reject and confirm "Reject this request?".

Troubleshooting

Symptom Cause What to do
"A follow-up or task needs a due date." Kind Follow-up or Task without Due on. Enter a due date.
"Only the assignee or the person who created this activity can change it." You are neither. Ask the assignee or creator.
"This activity is already closed." It was completed or cancelled meanwhile. Reload My queue.
No way to cancel an activity The screen has no Cancel button. Complete it with an outcome that explains it, or ask an administrator.
"Tick the required checklist items first." A required checklist item is open. Tick the items that are not marked optional.
"Complete the task this one depends on first." The task depends on another open task. Complete the other task first.
"Choose an owner or a team." Both New owner and Team were empty. Choose one.
"Snooze until a time within the next 14 days." The time is in the past or too far ahead. Pick a UTC time within 14 days.
"This alert cannot move to resolved from resolved." Someone resolved it already. Reload Alerts.
An alert you expect is missing Alerts come from the scheduled scan; or it is snoozed or not yours. Check All and the Snoozed tab. Ask a developer to check the cron (Developer-assisted).
"The requester cannot decide their own request." You raised it. Another approver must decide.
"This request expired before it was decided. Ask for a new one." The expiry passed. The requester raises a new request.
Approve asks for a note The screen requires the note for both buttons. Type a short note.

3. Sales and CRM: enquiries, leads, customers, rates and commissions

This chapter covers the commercial front end: capturing leads and enquiries, the customer list and the Customer 360 page with credit control, the rates and tariffs workbench (rate search, charge calculation, selling suggestion, chargeable-weight policies and margin rules), and sales commissions.

Who uses it:

  • Sales representative: enquiries and leads (crm.enquiry.list, crm.enquiry.create), customers (crm.customer.view), activities (crm.activity.manage), selling tariffs (pricing.sell_rate.view), selling suggestions (pricing.quotation.create) and their own commission statement (sales.commission.view).
  • Sales manager: as Sales representative, plus account owners and customer financials (crm.customer.manage, crm.customer.financials.view), commission rules and settlements (sales.commission.manage, sales.commission.approve) and margin rule approval (pricing.margin.approve).
  • Pricing officer: tariffs and buy rates (pricing.sell_rate.view, pricing.sell_rate.configure, pricing.buy_rate.view, pricing.buy_rate.configure); reads enquiries (crm.enquiry.list).
  • Pricing manager: as Pricing officer, plus margin rules (pricing.margin.configure, pricing.margin.approve).
  • AR accountant: customers with financials and the credit limit (crm.customer.view, crm.customer.financials.view, crm.credit.manage).
  • Finance manager: as AR accountant, plus credit override decisions (crm.credit.approve); holds sales.commission.approve but cannot reach the commission screens (see the gaps file).
  • Company administrator: enquiries, leads, customers and owners, selling tariffs and policies.

References: Spec §5.1 (leads and opportunities), §5.2 (customer management), §5.3 (commercial handoff and commission rules), §4.1 and §15 (credit limits and exposure), §6.1 (buying rates), §6.2 (pricing engine). UML 07 (CRM pursuit), UML 08 (rate contracts, tariffs and pricing), UML 04 (partners and commercial profiles). Acceptance scenarios A02 (hidden buy cost), A06 (chargeable weight example), A25 (CSV formula safety).

When a form post is refused, a plain English page shows the status, the heading "The request could not be completed" (409 or 422) or "Not allowed" (403), the exact message and a Reference code. Use the browser's Back button to return; the values you typed may be lost. A successful post returns to the page without a success message.

Enquiries and leads

How to open it: sidebar Sales > Enquiries and leads, route /enquiries. Arabic: Leads and enquiries (العملاء المحتملون والاستفسارات).

Leads and enquiries

Leads and enquiries on a phone

The page title is Leads and enquiries, subtitle "Open enquiries are company records. No sample pipeline is shown." The left column has two lists; the right column has two forms.

  • Enquiries: Reference (for example E-…), Customer, Route (origin to destination) and Status, with a "Filter this page…" box that filters the rows already shown. Statuses: pricing (recorded directly or accepted from booking intake, waiting for a quotation) and qualification (converted from a lead). Enquiry rows have no detail page. Empty: "No enquiries recorded."
  • Leads: Reference (L-YYYY-000001), Company and Status, with row buttons. Statuses: new, assigned, contacted, qualified, disqualified and converted. The screen offers Qualify (for new, assigned and contacted leads) and Convert (for qualified leads). There are no buttons for assigned, contacted or disqualified. Empty: "No leads recorded."

Fields

New enquiry form

New enquiry (posts to /enquiries):

Field Required What to enter / rules
Customer Yes An active customer profile ("Select a customer").
Origin Yes Location code or name. Saved in capitals; 2 to 63 characters, letters, digits, spaces and hyphens, starting with a letter or digit.
Destination Yes Same rules as Origin.
Commodity Yes What is shipped. Up to 1000 characters.
Requirements Yes "Service, equipment, dates and any special handling." Up to 1000 characters.

New lead form

New lead (posts to /leads). The lead fields are generated from one list in the template (fact pack label $label [$name]); each is shown below by its label and submitted name:

Field Required What to enter / rules
Source (source) Yes "Where the lead came from, e.g. website, referral, trade show."
Company (company_name) Yes The prospect's company name.
Contact (contact_name) No Contact person.
Email (email) No Must be a valid email. Only one open lead per email (disqualified leads do not count).
Phone (phone) No Telephone.
Origin (origin) No on screen Needed later to convert the lead.
Destination (destination) No on screen Needed later to convert the lead.
Commodity (commodity) No on screen Needed later to convert the lead.

Row form on a qualified lead:

Field Required What to enter / rules
profile_id (shown as Customer, with Convert) Yes The existing customer profile the lead belongs to.

Actions

Button/link What it does Permission Confirmation Result
Save enquiry POST /enquiries crm.enquiry.create Leave-page warning if unsaved New enquiry, status pricing. Back on /enquiries.
Save lead POST /leads crm.enquiry.create Leave-page warning if unsaved New lead, status new, owned by you, next action "Qualify the lead". Audit lead.created.
Qualify POST /leads/{id}/qualify crm.enquiry.create None Status qualified.
Convert POST /leads/{id}/convert crm.enquiry.create None A new enquiry (status qualification, requirements "Converted from L-…") for the chosen customer; the lead becomes converted. Converting twice returns the same enquiry. Audit lead.converted.

Rules and messages

  • "Customer profile was not found." (404, customer missing or inactive)
  • "Origin is not valid." / "Destination is not valid."
  • "Commodity is required." / "Requirements is required." (also shown when the text is longer than 1000 characters)
  • "Source is required." / "Company name is required."
  • "Email is not valid."
  • "A lead with this email already exists: {reference}." (409)
  • "That lead transition is not allowed."
  • "Only a qualified lead can be converted." (409)
  • "Origin is required." / "Destination is required." / "Commodity is required." when converting a lead saved without them.
  • "Lead was not found." (404)

Role differences

  • Without crm.enquiry.list (for example Branch manager, Operations roles, AR accountant) the link is hidden and /enquiries shows "Not allowed".
  • Pricing officer and Pricing manager can see both lists and both forms but hold no crm.enquiry.create; saving, qualifying or converting ends in "You do not have permission for this action."

Customers

How to open it: sidebar Sales > Customers, route /customers. Arabic: Customers (العملاء).

Subtitle: "Company customer profiles. Open one for contacts, quotations, bookings, jobs, activities and, with financial permission, balances and credit." Empty search: "No customers match." / "Try a different name or code."

Customer list

The header has the buttons Commissions and My queue. The search form filters by name or code and shows "{n} shown". Columns: Customer (link to Customer 360), Code, Status, Payment terms and Account owner ("Not assigned" when empty). At most 200 rows are shown.

Fields

Field Required What to enter / rules
q (placeholder Name or code) No Part of the customer name or code. Submitted with GET.

Actions

Button/link What it does Permission Confirmation Result
Search GET /customers?q=… crm.customer.view None Filtered list.
Customer name Opens /customers/{id} crm.customer.view None Customer 360.
Commissions / My queue Open /commissions / /activities As for those pages None —

Rules and messages

Without crm.customer.view, /customers shows "Not allowed".

Role differences

Pricing officers, Pricing managers, operations roles, AP accountant, Treasury user, Executive and Auditor do not see the list.

Customer 360

How to open it: select a customer in Customers; route /customers/{id}. Arabic: Credit control (مراقبة الائتمان).

Customer 360 with numbered sections

Numbered parts:

  1. Overview, with the Set account owner form.
  2. Credit control: Limit, Exposure and Available, the exposure components and the overrides table.
  3. The credit limit form (Save limit).
  4. The override request form (Request override).
  5. Recent activity, with the Add activity form.

Customer 360 as seen by a Sales representative

Customer 360 in Arabic

The title is the customer's legal name (with the Arabic name under it if recorded) and its status. The Sections tabs jump within the page: Overview, Credit control and Receivables ageing (only with financial access), Commercial, Jobs and Recent activity.

  • Overview: Code, Payment terms ("not set"), Account owner and Collections owner ("not assigned").
  • Credit control (financial access only): with no limit, "No credit limit is set. Bookings and invoices are not credit-checked for this customer." With a limit: KPIs Limit ("Plus approved overrides {amount}"), Exposure and Available ("Within limit" or "Limit exceeded"), and a table of exposure components: Open posted receivables, Draft invoices, Unbilled job sell lines, Open bookings without job sell lines, Less unapplied receipts. "Other currencies are converted through the base currency at the latest company rate on or before {date}." Then the overrides table: Override, Valid until, Reason, Requested by, Status (pending, approved, rejected; "by {name}"), Actions.
  • Receivables ageing (financial access only): a total per currency and the open invoices with Invoice, Date, Days and Open amount ("No open receivables.").
  • Commercial: Contacts, Open quotations ("Revision {n}", "valid until {date}") and Bookings.
  • Jobs: jobs for this customer in your scope ("None in your scope.").
  • Recent activity: the latest activities and an add form.

Fields

Set account owner (posts to /customers/{id}/owner):

Field Required What to enter / rules
user_id (shown as Account owner) Yes An active internal member of this company.

Credit limit (posts to /customers/{id}/credit-limit):

Field Required What to enter / rules
amount (shown as Limit, placeholder "blank clears") No A positive amount. Blank removes the limit. Negative values are refused.
currency (shown as Currency) Yes when a limit is set An active currency code. Default EGP.
reason (shown as Reason for the change) Yes Up to 500 characters.

Credit override request (posts to /customers/{id}/credit-overrides; shown only once the customer has a limit):

Field Required What to enter / rules
amount (shown as Extra {currency}) Yes Extra headroom, greater than zero, in the limit currency.
valid_until (shown as Valid until) Yes A date, today or later.
reason (shown as Why the exception is needed) Yes Up to 500 characters.

Add activity (posts to /activities; same rules as My queue in chapter 2, and the activity is linked to this customer):

Field Required What to enter / rules
kind (shown as Kind) Yes Follow-up, Task, Call, Meeting or Note. A note is saved as done.
subject (shown as Subject) Yes Up to 200 characters.
due_on (shown as Due on) For Follow-up and Task A date.
assigned_to (shown as Assign to) No An active internal member; defaults to you.

Actions

Button/link What it does Permission Confirmation Result
Set account owner POST /customers/{id}/owner crm.customer.manage None Owner changed.
Save limit POST /customers/{id}/credit-limit crm.credit.manage and crm.customer.financials.view "Change the credit limit for this customer?" New limit (or cleared). Audit customer.credit_limit_changed.
Request override POST /customers/{id}/credit-overrides crm.credit.manage and crm.customer.financials.view None Override pending.
Approve / Reject (override row) POST /credit-overrides/{id}/approve or /credit-overrides/{id}/reject (fact pack: /credit-overrides/{id}/(approve|reject)) crm.credit.approve; not the requester (the buttons are hidden from the requester) "Approve this credit override?" / "Reject this credit override?" Status approved or rejected; back on Customer 360. No decision note is sent from this screen.
Add activity POST /activities crm.activity.manage None Activity linked to the customer; back on Customer 360.

Rules and messages

  • "Customer was not found." (404; also for a customer of another company)
  • "The account owner must be an active internal member of this company." (422)
  • "Reason is required (up to 500 characters)."
  • "Credit limit is not a valid amount." / "Credit limit cannot be negative."
  • "Credit limit currency is not an active currency." (422)
  • "Override amount must be greater than zero."
  • "Valid until must be a date." / "Valid until cannot be in the past." (422)
  • "Set a credit limit before requesting an override." (409)
  • "Credit override was not found." (404)
  • "This override was already decided." (409)
  • "Another person must decide a credit override you requested." (409)

Credit limits and overrides take effect at booking and invoicing (chapter 4 and the receivables chapter). The policy for which customers get which limit is an open question (OPEN_QUESTIONS Q14); the system does not set one.

Role differences

  • Without crm.customer.financials.view (Sales representative, Company administrator), Credit control and Receivables ageing are not shown and the limit is removed from the server response.
  • Sales manager sees credit figures but cannot change the limit (no crm.credit.manage).
  • AR accountant can change the limit and request overrides, but cannot decide them.
  • Finance manager can change limits, request overrides and decide overrides requested by someone else.
  • The owner form appears only with crm.customer.manage; the activity form only with crm.activity.manage.

Rates and tariffs

How to open it: sidebar Sales > Rates and tariffs, route /tariffs. The Quotations page has a Selling tariffs button that leads here. The page title is Tariffs (التعريفات). Arabic: Find rates (البحث عن الأسعار), Chargeable-weight policies (سياسات الوزن المحتسب), Margin rules (قواعد الهامش).

Rates and tariffs workbench with numbered sections

Numbered parts:

  1. Find rates.
  2. Calculate a charge.
  3. Selling suggestion.
  4. The Selling tariffs and Buy rates tables, with their add forms.
  5. Chargeable-weight policies.
  6. Margin rules.

Rates and tariffs as seen by a Sales representative

Subtitle: "Selling tariffs are stored separately from buying rates. Overlapping matches stay listed with priority and explanation; the engine does not choose one." The Sections tabs are Find rates, Calculate, Selling suggestion (only with pricing.quotation.create), Rates, Chargeable-weight policies and Margin rules.

  • Find rates shows, for Selling tariffs (and Buy rates if you may see them), one of: "No rate covers this lane, mode and date.", "One rate covers this lane, mode and date." or, when several overlap, "Several rates overlap at the same top priority. Choose one explicitly." / "Several rates overlap. Priorities and explanations are listed; choose one explicitly." Each candidate shows "#{id}, priority {priority}", the basis, amount, breaks and explanation.
  • Calculate a charge saves a calculation and shows "Calculation {id} ({kind} rate)" with the volume, volumetric and chargeable weight, "Billable {quantity}, unit rate {rate}", any "{limit} applied" (minimum or maximum) and the Amount. The calculation id can be linked to a quotation (chapter 4).
  • Selling suggestion: "Uses the buy rate for the lane and the approved margin rule. Cost is shown only to users who may see buy rates." The result shows "Suggested sell … for {n} billable unit(s), calculation {id}.", the rule ("Rule: {rule} (minimum margin {margin}%).") and, for buy-rate viewers only, "Cost {amount} {currency} from buy rate {id}." If several buy rates match, they are listed and you must give a buy rate number.
  • Selling tariffs and Buy rates tables: #, Provider (buy only), Lane, Mode, Basis, Rate, Min / max, Breaks, Validity, Priority and Explanation. Expandable forms Add selling tariff and Add buy rate appear for users who may configure them.
  • Chargeable-weight policies (one per mode): "Per mode. Nothing is assumed: a chargeable-kg or revenue-ton rate cannot be calculated until its mode has a policy." Columns Mode, Measure, Divisor, Volume factor, Method, Rounding, Pivot and Explanation.
  • Margin rules: Scope (mode or "any mode", charge or "any charge"), Markup, Minimum margin, Priority, Status and Explanation. Statuses: proposed (waiting for approval), active (used for suggestions and the margin floor), retired.

Fields

Find rates (GET /tariffs/match):

Field Required What to enter / rules
origin (shown as Origin) Yes Lane code, as stored on the rates.
destination (shown as Destination) Yes Lane code.
mode_name (shown as Mode) Yes air, ocean_fcl, ocean_lcl or road (suggestions offered).
service_date (shown as Service date) Yes The date the rate must cover.
date_basis (shown as Date basis) Yes Booking date or Departure date.

Calculate a charge (POST /tariffs/calculate):

Field Required What to enter / rules
rate_kind (shown as Rate) Yes Selling tariff, or Buy rate (only if you may see buy rates).
rate_id (shown as Rate number) Yes The # of the rate, from the tables or Find rates.
pieces (shown as Dimensions (cm), one line per size) Depends on basis One line per size: "pieces x length x width x height", in cm.
gross_kg (shown as Gross kg) Depends on basis Gross weight.
volume_cbm (shown as Volume cbm (if no dimensions)) Depends on basis Volume when no dimensions are given.
quantity (shown as Quantity (other bases)) Depends on basis Quantity for per-container, per-truck, per-km, per-day and per-unit bases.
discount_percent (shown as Agreed discount %) No Agreed discount.

Selling suggestion (POST /tariffs/suggest): Origin, Destination, Mode, Service date and Date basis as in Find rates, the shipment fields as in Calculate, and:

Field Required What to enter / rules
charge_code_id (shown as Charge code) No Limit the buy rates to one charge.
buy_rate_id (shown as Buy rate number (if several)) When several buy rates match The buy rate to use.

Add selling tariff form

Add selling tariff / Add buy rate (POST /tariffs/sell or /tariffs/buy; not listed in the fact pack coverage):

Field Required What to enter / rules
Charge code Yes An active charge code.
Provider Yes (buy only) Carrier or supplier, up to 500 characters.
Origin, Destination Yes Lane codes.
Mode Yes Lower-case mode name such as air or ocean_fcl.
Charge basis Yes fixed, per_shipment, per_document, per_piece, per_container, per_kg, per_chargeable_kg, per_cbm, per_revenue_ton, per_truck, per_km, per_day or per_unit.
Unit No A unit from reference data.
Rate Yes Greater than zero.
Currency Yes An existing currency (default USD).
Priority Yes 1 or more (default 10).
Minimum, Maximum No Greater than zero; Minimum not above Maximum.
Breaks No "One per line, as from:rate." Not allowed on flat bases.
Valid from, Valid to Yes Dates; Valid to not before Valid from.
Date basis Yes Booking date or Departure date.
Explanation Yes "Contract reference and why this rate applies", up to 500 characters.

Chargeable-weight policy (POST /tariffs/policies; saving a mode that already has a policy replaces it):

Field Required What to enter / rules
mode_name (shown as Mode) Yes The mode.
measure (shown as Measure) Yes Chargeable weight or Revenue ton.
divisor (shown as Divisor) See rules Weight: a divisor or a volume factor is needed. Revenue ton: kg per revenue ton.
volume_factor (shown as Volume factor) See rules kg per cbm.
method (shown as Method) Yes Aggregate or Per piece.
rounding_increment (shown as Round) No Rounding step, greater than zero.
pivot_weight (shown as Pivot) No Greater than zero.
explanation (shown as Explanation) Yes "Contract basis for this policy", up to 500 characters.

Margin rules panel

Margin rule (POST /margin-rules):

Field Required What to enter / rules
mode_name (shown as Mode) No "blank = any".
charge_code_id (shown as Charge) No "Any charge" or one charge code.
markup_percent (shown as Markup %) Yes Above 0, at most 1000.
min_margin_percent (shown as Minimum margin %) Yes 0 to 1000. The markup must give at least this margin (margin = markup × 100 / (markup + 100)).
priority (shown as Priority) Yes 1 or more.
explanation (shown as Explanation, placeholder "Why") Yes Up to 500 characters.

Actions

Button/link What it does Permission Confirmation Result
Find matches GET /tariffs/match pricing.sell_rate.view (buy list with pricing.buy_rate.view) None Candidate rates; nothing is saved.
Calculate POST /tariffs/calculate pricing.sell_rate.view (sell) or pricing.buy_rate.view (buy) None A saved calculation (audit pricing.calculated; buy amounts are logged as "[cost]").
Suggest selling price POST /tariffs/suggest pricing.quotation.create None A suggestion saved as a buy-rate calculation. Cost and markup hidden without pricing.buy_rate.view.
Save selling tariff / Save buy rate POST /tariffs/sell / /tariffs/buy pricing.sell_rate.configure / pricing.buy_rate.configure None New rate; the page opens at its row. Audit sell_tariff.created / buy_rate.created.
Save policy POST /tariffs/policies pricing.buy_rate.configure or pricing.sell_rate.configure None Policy saved or replaced. Audit chargeable_weight_policy.saved.
Propose rule POST /margin-rules pricing.margin.configure None Rule proposed. Audit margin_rule.proposed.
Approve (margin rule) POST /margin-rules/{id}/approve pricing.margin.approve; not the proposer (the button is hidden from the proposer) "Approve this margin rule?" Status active. Audit margin_rule.approved.
Retire (margin rule) POST /margin-rules/{id}/retire pricing.margin.configure "Retire this margin rule?" Status retired. Audit margin_rule.retired.

The fact pack groups the last two as /margin-rules/{id}/(approve|retire).

Rules and messages

  • "Origin is not valid." / "Destination is not valid." / "Mode is not valid." / "Service date must be a date." / "Date basis must be booking date or departure date." (Find rates errors appear on the plain error page.)
  • "Rate kind must be buy or sell."
  • "Rate was not found." (404)
  • "Each dimension line must be "pieces x length x width x height" in cm."
  • "No chargeable-weight policy is configured for mode {mode}." (409)
  • "The mode policy measure does not match the rate basis." (409)
  • "Dimensions or volume are required for this basis." / "Gross weight is required for this basis." / "Pieces are required for this basis." / "Quantity is required for this basis."
  • "The chosen buy rate does not cover this lane, mode and date."
  • "No approved margin rule covers this charge and mode." (409)
  • "Two approved margin rules apply equally. Adjust their priorities." (409)
  • Rates: "Charge basis is not supported.", "Currency is not valid.", "Unit is not in reference data.", "Minimum is above maximum.", "Valid to is before valid from.", "Priority must be at least 1.", "Each break line must be "from quantity:rate".", "A flat charge cannot have breaks.", "Break quantities must be unique.", "Charge code is not available.", "{label} is not a valid number.", "{label} must be greater than zero.", "{label} is required (500 characters at most)."
  • Policies: "Measure must be weight or revenue ton.", "Method must be aggregate or per piece.", "A revenue ton policy needs kg per revenue ton in the divisor.", "A weight policy needs a divisor or a volume factor."
  • Margin rules: "Markup is not a valid percentage." / "Markup is out of range.", "The markup gives a margin below the minimum margin.", "Priority must be at least 1.", "Only a proposed rule can be approved.", "The person who proposed a margin rule cannot approve it.", "The rule is already retired.", "Margin rule was not found." (404)

Role differences

  • Sales representatives see Selling tariffs, policies and margin rules (read only), can calculate selling tariffs and get selling suggestions without cost or markup. The Buy rates panel is not shown.
  • Pricing officers add tariffs, buy rates and policies and see buy costs; they cannot propose margin rules.
  • Pricing managers propose and retire margin rules and approve rules proposed by someone else. Sales managers can approve margin rules but not propose or retire them.
  • Company administrators add selling tariffs and policies but cannot see or add buy rates.

Commissions

How to open it: sidebar Sales > Commissions, route /commissions, or the Commissions button on Customers. Arabic: Commissions (العمولات), Statement (الكشف), Settlements (التسويات).

Commissions for a Sales manager

A Sales representative's own statement

Subtitle: "Commission accrues when an invoice posts, on its net amount, at the salesperson's approved rule; credit notes reverse it. It becomes payable once the invoice is fully collected. Accruals are a subledger and are not posted to the ledger."

  • Statement filter: Salesperson, From (default 1 January of this year), To (default today) and Show statement.
  • With a statement: the heading "Statement: {name}, {from} to {to}", Download CSV, a totals table per currency (Accrued, Awaiting collection, Eligible, In settlement, Settled) and the Accruals table: Date, Source (invoice, credit or debit note), Invoice, Customer, Basis, Rate %, Commission and State. State values: awaiting_collection (invoice still open), eligible (invoice fully collected, not yet settled), in_settlement (in a proposed settlement), settled (settlement approved). Empty: "No accruals in this range."
  • Settlements: #, Up to, Total, Proposed by, Status (proposed, approved, rejected; "by {name}") and Actions.
  • Rules (managers only): the propose form and the table Salesperson, Basis, Rate %, Valid ("{from} to {to}", "open"), Status (proposed, active, retired), Proposed by and Actions. Empty: "No commission rules recorded."
  • Users who may not read the chosen statement see "You can read your own statement only."

Fields

Statement filter (GET /commissions):

Field Required What to enter / rules
salesperson_id (shown as Salesperson) Yes Active internal member. Disabled (fixed to you) unless you hold sales.commission.manage.
from (shown as From) Yes Start date.
to (shown as To) Yes End date, not before From.

Propose settlement (managers, POST /commissions/settlements):

Field Required What to enter / rules
currency (shown as Currency) Yes Three-letter code, default EGP.
period_to (shown as Period end) Yes Settles eligible accruals dated up to this day.

Propose rule form

Propose rule (managers, POST /commissions/rules):

Field Required What to enter / rules
salesperson_id (shown as Salesperson) Yes Active internal member.
rate_percent (shown as Rate %) Yes Above 0 and at most 100.
valid_from (shown as Valid from) Yes Date.
valid_to (shown as Valid to) No Date, not before Valid from. Blank = open-ended.

Actions

Button/link What it does Permission Confirmation Result
Show statement GET /commissions?salesperson_id=…&from=…&to=… Own statement: sales.commission.view; anyone's: sales.commission.manage None The statement.
Download CSV GET /commissions?…&format=csv As for the statement None File commission-statement-{id}-{to}.csv with Salesperson, Accrued on, Source, Invoice, Credit/debit note, Customer, Currency, Basis, Rate %, Commission and State. Text that starts like a spreadsheet formula is neutralised.
Propose settlement of eligible commission POST /commissions/settlements sales.commission.manage "Propose a settlement of the eligible commission?" A proposed settlement with all eligible, unsettled accruals up to Period end in that currency. Audit commission_settlement.proposed.
Approve / Reject (settlement) POST /commissions/settlements/{id}/approve or /reject (fact pack: /commissions/settlements/{id}/(approve|reject)) sales.commission.approve; not the salesperson and not the proposer (buttons hidden from them) "Approve this settlement?" / "Reject this settlement?" Approved: accruals settled. Rejected: accruals released (eligible again).
Propose rule POST /commissions/rules sales.commission.manage None Rule proposed.
Approve (rule) POST /commissions/rules/{id}/approve sales.commission.approve; not the proposer and not the salesperson (button hidden from them) "Approve this commission rule?" Rule active.
Retire (rule) POST /commissions/rules/{id}/retire sales.commission.manage "Retire this commission rule?" Rule retired.

The fact pack groups rule actions as /commissions/rules/{id}/(approve|retire).

Rules and messages

  • Opening another salesperson's statement or CSV link without sales.commission.manage shows the page with "You can read your own statement only." and no file. The service message "You can only read your own commission statement." (403) is not reached from this screen.
  • "From must be a date." / "To must be a date." / "The end date is before the start date." (422)
  • "The rate must be above 0 and at most 100 percent." (422)
  • "Valid to is before valid from." (422)
  • "The salesperson must be an active internal member of this company." (422)
  • "Commission rule was not found." (404) / "Only a proposed rule can be approved." (409) / "This rule is already retired." (409)
  • "A commission rule is approved by someone other than its proposer and the salesperson." (409)
  • "Another active rule for this salesperson overlaps these dates. Retire it first." (409)
  • "There is no collected, unsettled commission to settle." (409)
  • "Settlement was not found." (404) / "This settlement was already decided." (409)
  • "A salesperson cannot approve their own commission settlement, and the proposer cannot decide it." (409)

Role differences

  • Sales representative: own statement and CSV only; the Salesperson list is disabled; no Rules panel or settlement controls.
  • Sales manager: any salesperson's statement, rules and settlements; approves rules and settlements proposed by someone else for someone else.
  • Finance manager: holds sales.commission.approve but no view or manage right, so the page shows only "You can read your own statement only." and no approval buttons.
  • /commissions never returns "Not allowed": any internal user can open it and sees the notice.

Procedures

Record an enquiry for an existing customer

  1. Open Sales > Enquiries and leads.
  2. In New enquiry, choose the Customer.
  3. Enter Origin and Destination codes (for example EGALY and DEHAM), Commodity and Requirements.
  4. Select Save enquiry. The enquiry appears at the top of Enquiries with status pricing.

Capture, qualify and convert a lead

  1. In New lead, enter Source and Company, plus Origin, Destination and Commodity (needed to convert later).
  2. Select Save lead. The lead shows status new.
  3. When the lead is qualified, select Qualify in its row.
  4. Choose the customer profile in the row's list and select Convert. A new enquiry with status qualification appears.

Set or change a customer's credit limit

  1. Open Sales > Customers and select the customer.
  2. Select Credit control in the section tabs.
  3. Enter Limit and Currency (leave Limit blank to remove it) and the Reason for the change.
  4. Select Save limit and confirm "Change the credit limit for this customer?".

Request and approve a temporary credit override

  1. As AR accountant, open the customer's Credit control.
  2. Enter Extra {currency}, Valid until and Why the exception is needed; select Request override.
  3. As Finance manager, open the same customer. In the override row select Approve and confirm.
  4. The Limit KPI shows "Plus approved overrides {amount}".

Calculate an air charge

  1. Make sure the air mode has a chargeable-weight policy (Chargeable-weight policies).
  2. In Calculate a charge, choose Rate Selling tariff and enter the Rate number.
  3. Enter Dimensions (cm), one line per size, for example 2 x 120 x 80 x 100, and Gross kg.
  4. Select Calculate. Note the calculation number to link it on a quotation.

Propose and approve a margin rule

  1. As Pricing manager, in Margin rules enter Markup %, Minimum margin %, Priority and Why; select Propose rule.
  2. As a different user with pricing.margin.approve (another Pricing manager or a Sales manager), select Approve and confirm "Approve this margin rule?".

Settle commission

  1. As Sales manager, open Commissions, choose the Salesperson and dates, select Show statement.
  2. Check the Eligible total, set Currency and Period end, select Propose settlement of eligible commission and confirm.
  3. A different user with sales.commission.approve (and Commissions access) selects Approve in Settlements and confirms.

Troubleshooting

Symptom Cause What to do
"Origin is not valid." The code has characters other than letters, digits, spaces or hyphens, or is 1 character. Use the location code, for example EGALY.
"A lead with this email already exists: L-…" An open lead uses that email. Open the existing lead.
"Origin is required." when converting The lead was saved without origin, destination or commodity, and leads cannot be edited. Record a new enquiry for the customer instead.
Save enquiry gives "You do not have permission for this action." Pricing roles can see but not create enquiries. Ask a sales user.
Credit control section missing You lack customer financial access. Ask an AR accountant or Finance manager.
"Set a credit limit before requesting an override." No limit exists. Set a limit first.
No Approve or Reject buttons on a pending override You requested it, or you lack crm.credit.approve. Ask another approver (Finance manager).
"No chargeable-weight policy is configured for mode air." The mode has no policy. A pricing user saves one in Chargeable-weight policies.
"Several rates overlap at the same top priority. Choose one explicitly." Two rates with equal priority cover the lane. Use a specific Rate number, or ask pricing to adjust priorities.
"No approved margin rule covers this charge and mode." No active rule. A Pricing manager proposes one and someone else approves it.
No Approve button on a proposed margin rule You proposed it, or you lack pricing.margin.approve. Ask another Pricing manager or a Sales manager.
"You can read your own statement only." No commission view or manage right for that salesperson. Ask a Sales manager.
"There is no collected, unsettled commission to settle." No eligible accruals up to Period end in that currency. Check the Eligible column and currency.

4. Quotations, bookings and booking intake

This chapter covers turning an enquiry into a quotation, issuing and revising it, recording the customer's acceptance, creating a booking from the acceptance, opening a job from the booking, and reviewing booking requests that arrive by email or through the customer API.

Who uses it:

  • Sales representative: quotations (pricing.quotation.view, pricing.quotation.create, pricing.quotation.issue, pricing.quotation.accept, pricing.quotation.print), bookings (bookings.booking.view, bookings.booking.create) and the intake queue (bookings.intake.review).
  • Sales manager: as Sales representative, plus intake API tokens (bookings.intake.configure) and issuing below the margin floor (pricing.margin.approve).
  • Pricing officer: prepares and prints quotations with buy estimates (pricing.quotation.create, pricing.quotation.print, pricing.buy_rate.configure); cannot issue.
  • Pricing manager: as Pricing officer, plus issuing and margin approval (pricing.quotation.issue, pricing.margin.approve).
  • Operations coordinator, Operations manager, Branch manager and Customs coordinator: read bookings (bookings.booking.view) and open jobs from them (operations.job.create).
  • Company administrator: everything in this chapter except margin approval and buy estimates.

References: Spec §5.3 (commercial handoff), §6.2 (pricing engine and margin floor), §6.3 (quotation behaviour), §7.1 (booking intake), §7.2 (job creation), §15 (credit exposure) and §24 (APIs and integrations). UML 09 (quotation revision, acceptance and bookings), UML 31 (quotation revision lifecycle), UML 32 (booking lifecycle), UML 23 (API clients and tokens). Acceptance scenarios A02 (customer outputs carry no buy cost or margin), A04 (no double booking of accepted quantities), A05 (a revision after acceptance leaves the booking snapshot unchanged) and A29 (Arabic quotation output).

When a form post is refused, a plain English page shows the status, the heading ("The request could not be completed" for 409 and 422, "Not allowed" for 403, "Not found" for 404), the exact message and a Reference code. Use Back to return. A successful post returns to the page without a success message.

Quotations

How to open it: sidebar Sales > Quotations, route /quotations. Arabic: Recorded quotations (عروض الأسعار المسجلة), New quotation (عرض سعر جديد).

Quotations list and New quotation form

Subtitle: "Selling amounts are entered by the user. Matching tariffs are suggestions and are not applied automatically." The header button Selling tariffs opens /tariffs (chapter 3).

  • Recorded quotations: Reference (Q-…), Enquiry, Route and Latest status (the status of the newest revision), with "Filter this page…". Empty: "No quotations recorded." / "Create a draft from an enquiry with the form below."
  • Revision statuses: draft (being prepared, not visible to the customer), issued (sent to the customer, cannot be edited), accepted (the customer accepted at least part of a line), expired (an acceptance was attempted after Valid until).
  • New quotation: "Creates a draft with one charge line. Further lines and options are added on the quotation." The quotation page has no control to add lines or options (see the gaps file), so a quotation created here has exactly one line.

Fields

New quotation form

Fieldset Enquiry and charge:

Field Required What to enter / rules
enquiry_id (shown as Enquiry) Yes An enquiry of this company. The list is empty without crm.enquiry.list.
charge_code_id (shown as Charge) Yes An active charge code.
quantity (shown as Quantity) Yes Greater than zero.
unit_code (shown as Unit) Yes A unit code from reference data (default cbm).
sell_amount (shown as Sell amount) Yes Unit selling price, greater than zero.
sell_currency (shown as Currency) Yes Three-letter currency code (default EGP).
buy_amount (shown as Buy estimate, optional) No Shown only with pricing.buy_rate.configure. Needed for the margin floor check.
buy_currency (shown as Buy currency, optional) With Buy estimate Three-letter currency. A different currency from the sell currency is not checked against the floor.

Fieldset Terms:

Field Required What to enter / rules
valid_until (shown as Valid until) Yes Last day the customer may accept.
incoterm (shown as Incoterm) No An Incoterm code from reference data (default FOB).
pricing_calculation_id (shown as Pricing calculation, optional) No "The id of a saved pricing calculation to link, if any." (the Calculation number from Rates and tariffs).
scope (shown as Service scope) Yes What is included. Up to 1000 characters.
exclusions (shown as Exclusions) Yes What is excluded. Up to 1000 characters.

Actions

Button/link What it does Permission Confirmation Result
Save draft POST /quotations pricing.quotation.create (buy estimate kept only with pricing.buy_rate.configure) Leave-page warning if unsaved Quotation with revision 1 in draft; opens /quotations/{id}. Audit quotation.created.
Reference link Opens /quotations/{id} pricing.quotation.view None Quotation detail.
Selling tariffs Opens /tariffs pricing.sell_rate.view None Chapter 3.

Rules and messages

  • "Valid until must be a date."
  • "Service scope is required." / "Exclusions is required." (also over 1000 characters)
  • "Incoterm is not valid." / "Incoterm is not in reference data."
  • "Unit is not valid." / "Unit is not in reference data."
  • "Quantity is not a valid amount." / "Quantity must be greater than zero."
  • "Sell amount currency is not valid." / "Currency is not in reference data." / "Sell amount must be greater than zero."
  • "Buy estimate currency is not valid." / "Buy estimate must be greater than zero."
  • "Charge code is not available."
  • "Pricing calculation was not found."
  • "Enquiry was not found." (404)

Role differences

  • Without pricing.quotation.view (operations, finance and administration roles other than Company administrator) the link is hidden and the page shows "Not allowed".
  • Sales users and Company administrators do not see Buy estimate or Buy currency.

Quotation detail

How to open it: select a reference in Recorded quotations; route /quotations/{id}. Arabic: Issue revision (إصدار المراجعة), New revision (مراجعة جديدة).

Quotation detail with numbered parts

Numbered parts:

  1. Header actions: Customer copy, PDF (English), PDF (العربية).
  2. The revision's terms: Valid until, Service scope and Exclusions.
  3. The accept row under a line.
  4. New revision on the latest issued or accepted revision.

Accepted revision with the Create booking form

Quotation detail on a phone

The page shows one panel per revision, newest first ("Revision {n}" with its status; older revisions are greyed). Each panel shows Valid until, Service scope and Exclusions, then the charge lines: Charge, Quantity, Sell and, for users with pricing.buy_rate.view, Buy estimate. "Issued below the margin floor with approval" marks a revision issued with a margin exception. Acceptances show "Accepted {date}".

Fields

Issue revision form

Issue form (draft revision, with pricing.quotation.issue):

Field Required What to enter / rules
margin_exception_note (shown as Margin exception note, optional) Only when a line is below the margin floor "Only if a line is below the margin floor; entered by a pricing approver, not the preparer."

Accept row (under each line of an issued or accepted revision, with pricing.quotation.accept):

Field Required What to enter / rules
quantity (shown as Accepted quantity) Yes Greater than zero and not more than the quantity still open on the line.
evidence (shown as Acceptance evidence) Yes How the customer accepted (for example "Email from buyer 3 Nov"). Up to 1000 characters.

Hidden: version_id and line_id identify the revision and line.

Create booking (under an acceptance, with bookings.booking.create):

Field Required What to enter / rules
customer_reference (shown as Customer reference) No The customer's own reference, such as a PO number.

Actions

Button/link What it does Permission Confirmation Result
Issue revision POST /quotations/{id}/issue pricing.quotation.issue; below the floor also pricing.margin.approve, a user other than the preparer, and a note "Issue this revision to the customer? An issued revision cannot be edited." Revision issued. Audit quotation.issued. A refused margin breach is audited as a failure.
New revision POST /quotations/{id}/revise pricing.quotation.create "Start a new revision of this quotation?" A new draft revision with copies of the latest issued or accepted revision's lines; the old revision keeps its status. Audit quotation.revised.
Accept POST /quotations/{id}/accept pricing.quotation.accept None Acceptance recorded; revision status accepted. Audit quotation.accepted.
Create booking POST /acceptances/{id}/book bookings.booking.create None Draft booking with the accepted snapshot; opens /bookings/{id}. Credit is checked first.
Customer copy Opens /quotations/{id}/customer pricing.quotation.view None The customer copy screen.
PDF (English) / PDF (العربية) GET /quotations/{id}/pdf?lang=en or ?lang=ar pricing.quotation.print None Downloads {reference}-R{revision}-{lang}.pdf. Audit quotation.pdf_generated the first time, quotation.pdf_downloaded afterwards.

Rules and messages

  • Issue: "Only a draft revision can be issued." (409), "A quotation needs at least one line." (422), "A line is below the approved margin floor. A pricing approver other than the preparer must issue it with a note." (409).
  • New revision: "A new revision can be opened from an issued or accepted revision." (409), "The issued terms could not be read." (409).
  • Accept: "Accepted quantity is not a valid amount." / "Accepted quantity must be greater than zero.", "Acceptance evidence is required.", "There is no issued revision to accept." (409), "This revision is past its validity date." (409; the revision is marked expired), "Quotation line was not found on the issued revision." (404), "Accepted quantity exceeds the remaining quoted quantity." (409).
  • Book: "Acceptance was not found." (404), "The accepted revision is missing.", "The acceptance has no quantities.", "An accepted line is missing.", "Accepted quantity is already allocated." (409), "This would take the customer over the credit limit ({currency} {limit}, available {available}). Ask for an approved credit override." (409, audited), "Credit exposure needs a {currency} exchange rate on or before {date}." (409).
  • PDF: "Language must be English or Arabic.", "No issued revision is available for the customer copy." (409), "The stored document belongs to different quotation lines." (409).
  • "Quotation was not found." (404; also for another company's quotation).

The margin floor applies only to lines with a buy estimate in the same currency as the sell amount (chapter 3, Margin rules).

Role differences

  • Pricing officers see buy estimates and the PDF links but no Issue form, accept rows or Create booking.
  • Sales representatives issue, accept and book but do not see buy estimates; a margin breach must be issued by a Sales manager or Pricing manager other than the preparer.
  • Without pricing.quotation.print the PDF links are hidden.

Customer copy

How to open it: Customer copy on the quotation; route /quotations/{id}/customer. Arabic: Customer copy (نسخة العميل).

Customer copy

Title "Customer copy {reference}", subtitle "This copy omits buying estimates, margin, and approval notes. It is not a PDF." It shows the latest issued or accepted revision: Revision, Valid until, Scope, Exclusions and Exchange, then per option the Charge, Quantity and Amount. Print uses the browser's print dialog.

Fields

None.

Actions

Button/link What it does Permission Confirmation Result
Print Browser print pricing.quotation.view Browser print dialog Printed copy without buy or margin fields.

Rules and messages

  • "No issued revision is available for the customer copy." (409) when only a draft exists.

Role differences

Any user with pricing.quotation.view sees the same copy; buy estimates are never shown here (A02).

Bookings

How to open it: sidebar Sales > Bookings, route /bookings. Arabic: Bookings (الحجوزات).

Bookings list

Subtitle: "A booking stays in draft until carrier and customer confirmation are recorded. Those confirmations are not installed." Columns Reference, Customer reference and Status. Every booking created by the system is in draft; there is no step that confirms it. Empty: "No bookings recorded." / "A booking is created when a customer accepts a quotation."

Fields

None.

Actions

Button/link What it does Permission Confirmation Result
Reference link Opens /bookings/{id} bookings.booking.view None Booking detail.

Rules and messages

Without bookings.booking.view the page shows "Not allowed".

Role differences

Sales, Sales manager, Company administrator and the operations roles (Operations coordinator, Operations manager, Branch manager, Customs coordinator) see the list.

Booking detail

How to open it: select a booking, or after Create booking; route /bookings/{id}. Arabic: Accepted snapshot (اللقطة المقبولة), Open a job (فتح ملف).

Booking detail with the Open a job form

Subtitle: "This is the accepted quotation snapshot. A later quotation revision does not change it."

  • Accepted snapshot: Quotation, Revision, Snapshot taken, Evidence, Scope and Exclusions.
  • Accepted charges: Charge, Accepted quantity and Sell. Empty: "No charges in this snapshot."
  • Job: a "Job {reference}" button when a job exists and you may view jobs; otherwise the Open a job form for users who may create jobs ("Add an active branch before opening a job." when there is no branch); otherwise "No job is linked to this booking."

Fields

Open a job form

Field Required What to enter / rules
branch_id (shown as Branch) Yes An active branch of this company.
mode_name (shown as Mode) Yes Ocean, Air or Road.
direction_name (shown as Direction) Yes Export, Import, Domestic or Cross.

Hidden: category_name is always forwarding.

Actions

Button/link What it does Permission Confirmation Result
Open job POST /bookings/{id}/job operations.job.create "Open a job from this booking?" Job, shipment, parties, cargo, leg and milestones created; opens /jobs/{id} (chapter 5). The customer's credit standing is recorded but does not block. Audit job.created.
Job {reference} Opens /jobs/{id} operations.job.view None The job.

Rules and messages

  • "Booking was not found." (404)
  • "Branch was not found." (404)
  • "Mode is not valid." / "Direction is not valid." / "Category is not valid."
  • "The booking snapshot has no lines." (409)
  • "This booking is already allocated to a job." (409)
  • "The booking needs a distinct origin and destination." (422)

Role differences

  • Operations roles and Company administrators open jobs and see the job link.
  • Sales users see the snapshot but never the job link (they lack operations.job.view), so the panel always says "No job is linked to this booking." even when a job exists (see gaps).

Booking intake

How to open it: sidebar Sales > Booking intake, route /intake. Arabic: Booking intake (استقبال الحجوزات), Intake status (حالة الاستقبال), API tokens (رموز الواجهة البرمجية).

Booking intake queue

Booking intake in Arabic

Subtitle: "Booking requests received by email and by the customer API. Each one waits here for review: accepting opens an enquiry, rejecting records why. No booking or job is created from a message."

  • Intake status tabs: Pending (default), Accepted, Rejected and All (/intake?status=pending|accepted|rejected|all).
  • Queue columns: Received, Channel (email or api), From ("Ref {reference}" for API requests), Subject ("(no subject)"), Customer ("Not matched" when the sender is not linked to a customer) and Status (with the enquiry reference once accepted). Empty: "Nothing here."
  • Mailboxes: Mailbox, Last poll ("never"), Outcome, Last message uid and Detail. With no polls: "No intake mailbox has been polled for this company. Mailboxes are configured in the private config file under {key} and read by the scheduled cron task." Mailbox setup is Developer-assisted.
  • API tokens (only with bookings.intake.configure): explains that each token belongs to one customer and requests go to POST /api/v1/intake/bookings with a Bearer token, a JSON body with client_reference, origin, destination and commodity, and an optional Idempotency-Key header. Table: Name, Customer, Prefix (fit_xxxxxxxx_…), Status (active or revoked), Last used ("never") and Actions. Empty: "No tokens issued."

Fields

Issue token form

Field Required What to enter / rules
status (Intake status tab) No pending, accepted, rejected or all.
profile_id (shown as Customer) Yes The customer the token belongs to.
name (shown as Token name) Yes 3 to 128 characters, for example TMS integration.

Actions

Button/link What it does Permission Confirmation Result
Intake status tab GET /intake?status=… (fact pack lists /intake as POST; it is a GET page) bookings.intake.review None Filtered queue.
Subject link Opens /intake/{id} bookings.intake.review None Intake message.
Issue token POST /intake/tokens bookings.intake.configure None The page shows "Copy this token now. It is shown once and cannot be recovered:" with the token and a Copy button. Audit intake_token.issued. Only a hash is stored.
Revoke POST /intake/tokens/{id}/revoke bookings.intake.configure "Revoke this token? Requests using it will be refused." Status revoked; back at /intake#tokens. Audit intake_token.revoked. Revoking an already revoked token changes nothing. API calls with it get HTTP 401 "The intake token is missing, unknown or revoked."

Do not refresh the page after Issue token: the browser offers to send the form again, and doing so issues a second token.

Rules and messages

  • "Name the token (3 to 128 characters)." (422)
  • "Customer profile was not found." (404)
  • "The token was not found." (404)

API callers (not this screen) receive: "client_reference is required: 1 to 128 letters, digits, dot, dash, underscore or slash.", "ready_date must be YYYY-MM-DD.", "Idempotency-Key must be 1 to 255 visible ASCII characters.", "This client_reference was already used for a different request." and "This Idempotency-Key was already used for a different request." (409). A repeated identical request returns the original message instead of a new one.

Role differences

  • Sales representatives review the queue but do not see API tokens.
  • Sales managers and Company administrators also issue and revoke tokens.
  • Other roles get "Not allowed".

Intake message

How to open it: select a subject in the queue; route /intake/{id}. Arabic: Accept into an enquiry (القبول كاستفسار).

Picture not taken (04-intake-message-annotated). The demo has no intake message. The reason is in the screenshot index.

Numbered parts:

  1. The Message panel.
  2. The Accept into an enquiry form.
  3. The Reject form.

The title is the subject. Message shows Received, Channel, From, Client reference, Customer ("Not matched. Choose one when accepting." when unknown), Message id and the body. A reviewed message shows "Accepted by {name} on {date}. Enquiry {reference} was opened for {customer}." with Open enquiries, or "Rejected by {name} on {date}: {reason}", and no forms.

Fields

Accept into an enquiry ("Check the details read from the message. They are saved only when you accept."):

Field Required What to enter / rules
profile_id (shown as Customer) Yes when not matched "Choose" a customer. Disabled when the message is already linked to a customer.
origin (shown as Origin) Yes Same rules as an enquiry origin (chapter 3).
destination (shown as Destination) Yes Same rules.
commodity (shown as Commodity) Yes Up to 1000 characters.
requirements (shown as Requirements) No Prefilled with mode, Incoterm, ready date, pieces, weight, volume and notes from the message. Blank becomes "Booking request {reference}".

Reject:

Field Required What to enter / rules
reason (shown as Reason) Yes 3 to 500 characters, for example "spam or duplicate of an existing enquiry".

Hidden: version detects changes made by someone else since you opened the page.

Actions

Button/link What it does Permission Confirmation Result
Open the message GET /intake/{id} (fact pack lists /intake/{id} as POST; it is a GET page) bookings.intake.review None The message.
Accept and open enquiry POST /intake/{id}/accept bookings.intake.review and crm.enquiry.create None An enquiry (status pricing) is created and the message becomes accepted. Audit intake.accepted. No booking or job is created.
Reject POST /intake/{id}/reject bookings.intake.review "Reject this message?" Message rejected with the reason. Audit intake.rejected.

Rules and messages

  • "The intake message was not found." (404)
  • "This message was already reviewed." (409)
  • "This message changed since you opened it. Reload and try again." (409)
  • "This message already belongs to a customer and cannot be moved to another." (409)
  • "Choose the customer for this message." (422)
  • "Give a reason of 3 to 500 characters." (422)
  • Enquiry rules from chapter 3: "Origin is not valid.", "Destination is not valid.", "Commodity is required.", "Customer profile was not found."
  • Failed accept or reject attempts are audited as failures.

Role differences

Without crm.enquiry.create the accept form is replaced by "You can reject this message. Accepting it needs permission to create enquiries."

Procedures

Prepare and issue a quotation

  1. Open Sales > Quotations.
  2. In New quotation choose the Enquiry and Charge, enter Quantity, Unit, Sell amount and Currency.
  3. Enter Valid until, Service scope and Exclusions; optionally Incoterm and Pricing calculation.
  4. Select Save draft. The quotation opens with Revision 1 in draft.
  5. Select Issue revision and confirm. Revision 1 shows issued.

Issue a quotation below the margin floor

  1. The preparer (for example a Pricing officer) saves the draft with a Buy estimate in the sell currency.
  2. If issuing shows "A line is below the approved margin floor…", a Pricing manager or Sales manager who did not prepare it opens the quotation.
  3. They enter the Margin exception note and select Issue revision. The revision shows "Issued below the margin floor with approval".

Record acceptance and create a booking

  1. Open the issued quotation.
  2. In the accept row under the line, enter Accepted quantity and Acceptance evidence; select Accept.
  3. Under "Accepted {date}", enter the Customer reference if given and select Create booking.
  4. The booking opens in draft with the accepted snapshot.

Revise an issued quotation

  1. Open the quotation and select New revision; confirm.
  2. A new draft revision appears with the same lines. It cannot be edited on screen; issue it as it is, or record a new quotation.

Open a job from a booking

  1. As an operations user, open the booking.
  2. In Open a job choose Branch, Mode and Direction; select Open job and confirm.
  3. The new job opens (chapter 5).

Review a booking request

  1. Open Sales > Booking intake (tab Pending) and select the subject.
  2. Check Origin, Destination, Commodity and Requirements; choose the Customer if not matched.
  3. Select Accept and open enquiry, or enter a Reason and select Reject.

Issue an API token for a customer

Staging only: test tokens against a staging company first. A token gives the customer's system the ability to create intake messages.

  1. As Sales manager, open Booking intake and go to API tokens.
  2. Choose the Customer, enter a Token name and select Issue token.
  3. Select Copy and pass the token to the customer's IT contact through a secure channel. It is not shown again.

Troubleshooting

Symptom Cause What to do
Enquiry list in New quotation is empty No enquiries, or you lack crm.enquiry.list. Record an enquiry first (chapter 3).
"A line is below the approved margin floor…" The sell price gives less margin than the approved rule. A different user with margin approval issues it with a note, or change the price on a new quotation.
No Issue revision button You lack pricing.quotation.issue or the revision is not a draft. Ask a sales user or Pricing manager.
"This revision is past its validity date." Valid until has passed; the revision is now expired. A new revision cannot be opened from an expired revision; record a new quotation.
"Accepted quantity exceeds the remaining quoted quantity." Earlier acceptances used the quantity. Accept only the remainder.
"This would take the customer over the credit limit…" Credit check at booking. Ask for a credit override (chapter 3).
"No issued revision is available for the customer copy." Only a draft exists. Issue the revision first.
"No job is linked to this booking." for a sales user Sales roles cannot view jobs. Ask operations for the job reference.
"This booking is already allocated to a job." A job was already opened. Open the job from the operations side.
"This message changed since you opened it. Reload and try again." Someone else reviewed or changed it. Reload the message.
Token lost Tokens are shown once. Revoke it and issue a new one.

5. Jobs and the job workspace

A job is the operational file for one shipment. It is opened from a confirmed booking (chapter 4) and then carries the shipment, its cargo, milestones, holds, costing, the house bill draft and the request for financial close. A job keeps four states apart: Operations (open or completed), Documents, Billing and Finance (open, under review, closed, reopened). Completing operations does not close finance.

Who uses it:

Role What the role does here Permission keys
Operations coordinator Opens the job list and workspace, records milestones and measurements, places and releases holds, completes and reopens operations, requests financial close, adds legs, tasks and purchase orders, sends messages operations.job.view, operations.job.complete, operations.shipment.view, operations.milestone.record, operations.cargo.measure, operations.transport_document.draft, operations.leg.manage, operations.task.view, operations.task.manage, operations.claim.view, operations.communication.send, operations.commitment.view, operations.commitment.manage
Operations manager Everything the coordinator does, plus job costing (sell estimates and their approval), leg exception approval, task templates, claims, email templates and purchase order approval adds operations.job_costing.manage, operations.sell_line.approve, operations.exception.approve, operations.task.configure, operations.claim.manage, operations.communication.configure, operations.commitment.approve
Branch manager, Customs coordinator Same job access as the coordinator (the customs coordinator also manages customs, chapter 7) as coordinator
Documentation officer, Warehouse or dispatch user Read the job and work on tasks (documents and custody are in chapter 7) operations.job.view, operations.task.view, operations.task.manage
Finance manager Reads the job, decides financial close, closes with an approved exception, approves claim reserves and settlements in Approvals operations.job.view, finance.job.close, finance.job.close_exception, finance.claim.approve, operations.claim.view
AR accountant Creates the customer invoice draft from approved sell lines and decides financial close; has no operations.job.view, so the job pages return 403 finance.invoice.draft, finance.job.close
Company administrator Broad operations access, including costing and configuration most operations.* keys

Sales, pricing, treasury, executive and auditor roles have no operations.* keys and do not see the Operations group.

Spec references: §7 (bookings and operations jobs), §12 (milestones, tasks, exceptions and approvals), §13 (communication), §14 (job costing). UML: 10, 12, 16, 17, 32, 33, 53.

Jobs list

Open it from the sidebar: Operations > Jobs and shipments (الملفات والشحنات). Route: /jobs. The page title is Jobs (الملفات). It needs operations.job.view.

Jobs list with the search box

Jobs list on a phone

The list shows the newest jobs first, 100 per page. Columns: Reference, Branch, Movement (mode icon, mode and direction), Operations, Billing, Finance. Each state is a coloured badge:

  • Operations: open (work in progress), completed (operations finished; may be reopened).
  • Billing: starts as not_ready.
  • Finance: open, under_review (financial close requested), closed, reopened.

The Consolidations button in the page header opens /consolidations (chapter 6).

Fields

Field Required What to enter / rules
q (search box, placeholder "Job reference") No Part of a job reference. The list shows jobs whose reference contains the text. At most 64 characters are used.

Actions

Button/link What it does Permission Confirmation Result
Search Filters the list by reference (GET) operations.job.view None Matching jobs; "No job reference contains that text." when none match
Job reference link Opens the job workspace /jobs/{id} operations.job.view None Job workspace
Consolidations Opens /consolidations operations.consolidation.view None Consolidations list

Rules and messages

  • An empty company shows "No jobs recorded."
  • A search with no match shows "No job reference contains that text."

Role differences

  • Roles without operations.job.view (for example AR accountant, Sales representative) do not see the sidebar link; typing /jobs returns 403 "You do not have permission for this action."
  • Jobs belong to the signed-in company. A job id of another company returns 404 "Job was not found."

Job workspace (Overview)

Open a job from the jobs list, or from any job link. Route: /jobs/{id}. The page title is the job reference; the badge next to it is the operations state. Section tabs appear below the header (see "Job section tabs").

Job workspace overview of an ocean export job

Job workspace with numbered areas

Numbered areas in the annotated shot: 1 summary header, 2 Cargo, 3 Milestones, 4 House bill, 5 Estimated margin, 6 Financial close.

Job workspace in Arabic

Job workspace on a phone

Summary header. Mode, Direction, Customer, Branch, Shipment, Operations, Documents, Billing, Finance and Credit check. The note under it explains that the credit check records the customer's credit standing when the job was opened; the credit gate is the booking confirmation, and this value does not approve or block the job.

Cargo (البضاعة). One row per cargo line with Quoted, Booked, Measured and Final quantities. When the measured quantity differs from the booked one, a warning says "No charge revision was created, and the final quantity stays empty." Users with operations.cargo.measure see a Measured quantity box and Record measurement.

Milestones (المراحل). The milestone plan for the job's mode and direction. Statuses: pending (not yet recorded), recorded, superseded (replaced by a correction). If the job has no plan yet, Apply milestone plan creates it. The plans configured in the system are:

Mode and direction Milestones in order
Ocean export booking_confirmed, cargo_received, gate_in, vgm_complete, shipping_instructions_submitted, loaded, departed, destination_handoff
Ocean import pre_alert_received, arrival_notice, arrived, discharge, delivery_order_available, customs_released, delivery, empty_returned
Air export booking_confirmed, cargo_received, security_screened, departed, destination_handoff
Air import pre_alert_received, arrived, customs_released, delivery
Road (any direction) booking_confirmed, departed ("Picked up and departed"), delivered

Ocean and air domestic or cross-trade jobs have no plan; Apply milestone plan then answers "No milestone template is configured for this mode and direction."

Only the next pending milestone can be recorded. Its form has Occurred (UTC) and Evidence for {milestone} (the milestone name is filled in), and the button Record milestone. Two milestones have their own forms:

  • departed – when the next milestone is departed, the form asks for Pieces, Occurred (UTC) and Departure evidence and the button is Record departure quantity. A partial quantity is recorded but does not mark the lot departed.
  • destination_handoff – when the first leg has no arrival yet, the form asks for Pieces, Occurred (UTC) and Arrival evidence and the button is Record arrival quantity.

For air and road jobs, departures and arrivals normally come from flight events (chapter 6) and trip stops (chapter 6).

Milestones panel with the record form

House bill (بوليصة الشحن الفرعية), anchor #house-bill. For an ocean shipment without a house bill, users with operations.transport_document.draft see the draft form. A draft shows Revise draft and Request release. Issue of the bill happens on the Documents tab (chapter 7).

House bill panel

Customer invoices (needs finance.invoice.view). Lists invoices created from this job. Users with finance.invoice.draft see a Tax select and the button Create invoice draft from {n} approved line(s), which opens the new draft at /invoices/{id} (chapter 8).

Estimated margin (الهامش المقدّر), anchor #estimated-margin (needs operations.job_costing.manage). Sell lines with status estimated (entered, not yet billable) or approved (approved for billing). Totals, margin notices and Recognised in the ledger (base currency). The sell estimate form adds new lines.

Estimated margin with the sell estimate form

Financial close (الإقفال المالي), anchor #financial-close (shown to users with operations.job.complete or finance.job.close). Shows Finance state: and the findings that block an ordinary close. Finding codes: cost_missing, cost_unapproved, allocation_stale, unbilled_sell, accrual_unmatched, recognition_pending. When there are none: "No open cost or billing findings." The notes say that open customer receivables do not block financial close. A history table lists each request (Requested, By, Outcome, Decided by, Reason, Reopened).

Financial close panel

Right column. Parties, Lots and legs, Holds (الإيقافات) and Operations (العمليات).

Hold statuses: active (blocks operational completion and, for its scope, release) and released. Hold types: general, document, finance, customs, claim. Hold scopes: all, cargo, documents.

Holds panel with the place-hold form

Fields

Measure cargo (/jobs/{id}/cargo/{id}/measure):

Field Required What to enter / rules
Measured quantity Yes Decimal greater than zero. "Measured quantity must be a decimal." / "Measured quantity must be greater than zero."

Record milestone (/jobs/{id}/milestones/record):

Field Required What to enter / rules
Occurred (UTC) Yes Date and time in UTC; not in the future. "Occurrence time must be a UTC date and time." / "Occurrence time cannot be in the future."
Evidence for Yes What proves the milestone (document number, gate-in slip, message reference).

Record departure quantity (/jobs/{id}/departure):

Field Required What to enter / rules
Pieces Yes Decimal greater than zero, not more than the lot that remains.
Occurred (UTC) Yes UTC, not in the future.
Departure evidence Yes Evidence of departure (bill of lading, sailing message).

Record arrival quantity (/jobs/{id}/arrival):

Field Required What to enter / rules
Pieces Yes Decimal greater than zero, not more than the lot that remains.
Occurred (UTC) Yes UTC, not before departure, not in the future.
Arrival evidence Yes Evidence of arrival.

Open house bill draft (/jobs/{id}/house-bill):

Field Required What to enter / rules
Freight terms Yes Prepaid or collect.
Shipper Yes Shipper as it should print, up to 500 characters.
Consignee Yes Consignee as it should print, up to 500 characters.

Create invoice draft (/jobs/{id}/invoice-draft):

Field Required What to enter / rules
Tax No Tax code applied to the invoice lines.

Record sell estimate (/jobs/{id}/sell-estimates):

Field Required What to enter / rules
Sell charge Yes An active sell charge code.
Quantity Yes Decimal greater than zero.
Unit Yes Unit of measure.
Unit rate Yes Decimal greater than zero.
Currency Yes Active currency.

Financial close decision (/jobs/{id}/close/(approve|return|reopen): /jobs/{id}/close/approve, /jobs/{id}/close/return, /jobs/{id}/close/reopen):

Field Required What to enter / rules
Exception reason Yes, when findings exist 10 to 500 characters. Disabled for users without finance.job.close_exception.
Return reason Yes 3 to 500 characters: what must be remediated.
Reopen reason Yes 3 to 500 characters (financial close reopen). On the Operations panel the same label is used for reopening operations (up to 500 characters).

Holds (/jobs/{id}/holds, /holds/{id}/release):

Field Required What to enter / rules
Hold type Yes general, document, finance, customs or claim.
Hold scope Yes all, cargo or documents. A cargo hold blocks cargo release; a documents hold blocks document release; all blocks both.
Hold reason Yes Why the job is held.
Release reason Yes Why the hold is lifted.

Actions

Button/link What it does Permission Confirmation Result
Record measurement Saves the measured quantity of a cargo line (/jobs/{id}/cargo/{id}/measure) operations.cargo.measure None Measured column filled; warning if it differs from booked
Apply milestone plan Creates the pending milestones (/jobs/{id}/milestones) operations.milestone.record None Milestone list appears
Record milestone Records the next pending milestone (/jobs/{id}/milestones/record) operations.milestone.record None Milestone recorded; tasks from matching task templates are created
Record departure quantity Records departed pieces (/jobs/{id}/departure) operations.milestone.record None Departed quantity; departed recorded only when the whole lot has left
Record arrival quantity Records arrived pieces (/jobs/{id}/arrival) operations.milestone.record None Arrived quantity; handoff recorded only when the whole lot has arrived
(no button) milestone correction /jobs/{id}/milestones/correct supersedes a recorded milestone operations.milestone.record – No screen offers it (see gaps); Developer-assisted
Open house bill draft Creates the house bill draft for an ocean shipment (/jobs/{id}/house-bill) operations.transport_document.draft None Draft with revision 1
Revise draft Copies the draft into a new revision (/transport-documents/{id}/revise, see chapter 7) operations.transport_document.draft None Revision count increases
Request release Posts to /transport-documents/{id}/release (see chapter 7) operations.transport_document.draft "Request release of this house bill?" Always refused for a draft (see Rules)
Create invoice draft from {n} approved line(s) Builds a customer invoice draft (/jobs/{id}/invoice-draft) finance.invoice.draft None Opens /invoices/{id} (draft, not posted)
Record sell estimate Adds an estimated sell line (/jobs/{id}/sell-estimates) operations.job_costing.manage None Line in Estimated margin
Approve for billing Approves one estimated line (/sell-lines/{id}/approve) operations.sell_line.approve "Approve this sell line for billing?" Line approved; counts for the invoice draft
Request financial close /jobs/{id}/close operations.job.complete "Request financial close for this job?" Finance state under_review
Approve financial close /jobs/{id}/close/approve with no findings finance.job.close "Close this job financially?" Finance state closed
Close as approved exception /jobs/{id}/close/approve with findings and an exception reason finance.job.close and finance.job.close_exception "Close this job financially?" Finance state closed, outcome closed by exception
Return for remediation /jobs/{id}/close/return finance.job.close None Finance state back to open (or reopened)
Reopen financial close /jobs/{id}/close/reopen finance.job.close "Reopen the financial close of this job?" (danger) Finance state reopened
Place hold /jobs/{id}/holds operations.job.complete None Hold active
Release hold /holds/{id}/release operations.job.complete "Release this hold?" Hold released
Complete operations /jobs/{id}/complete operations.job.complete "Complete operations for this job?" Operations completed
Reopen /jobs/{id}/reopen with Reopen reason operations.job.complete "Reopen operations for this job?" Operations open

Staging only: approving, closing by exception and reopening a financial close change the finance state other users rely on. Run these tests on a staging copy.

Rules and messages

Milestones and quantities:

  • "No milestone template is configured for this mode and direction."
  • "This milestone is not waiting to be recorded." / "Record earlier milestones first."
  • "A partial departure does not record the whole lot as departed." / "A partial arrival does not record destination handoff."
  • "Destination handoff cannot occur before arrival."
  • "Departure quantity must be a decimal." / "Departure quantity must be greater than zero." / "Departure is not waiting on this shipment." / "Departure quantity is larger than the remaining lot."
  • "Arrival quantity must be a decimal." / "Arrival quantity must be greater than zero." / "Record departure before arrival." / "Arrival is not waiting on this shipment." / "Arrival cannot occur before departure." / "Arrival quantity is larger than the remaining lot."
  • "The shipment has no lot."
  • Correction route: "Record the milestone before correcting it."
  • Measurement: "Cargo was not found."

House bill:

  • "A house bill draft is for an ocean shipment."
  • "This shipment already has a house bill draft."
  • "Freight terms must be prepaid or collect." / "Shipper is required." / "Consignee is required."
  • Request release on a draft: "A draft cannot be released. Original-document and financial holds are separate, and this draft clears neither." On an issued bill: "Release the original documents from the job release page, which checks document and finance holds."

Costing and invoice draft:

  • "Quantity and rate must be decimals." / "Quantity and rate must be greater than zero." / "Charge, unit, and currency must be active records."
  • "The job is under review for financial close; reopen it before adding charges."
  • "Only an estimated sell line can be approved." / "The person who entered a sell estimate cannot approve it." / "Sell estimate was not found."
  • "There are no approved, unbilled sell lines on this job." / "All sell lines on one invoice must share a currency. Approve the lines in one currency per invoice." / "The customer profile must be active before it can be invoiced."

Operations and financial close:

  • "Only an open job can be operationally completed." / "An active hold blocks operational completion." / "Every leg of a multimodal job must arrive before operational completion."
  • "Only a completed job can be reopened." / "Reason is required." / "Financial close is under review; finance must return or reopen it first."
  • "Operations must be completed before financial close is requested." / "Financial close is already …" (under review or closed).
  • "The job has open findings: …" (ordinary approve with findings).
  • "An exception needs a reason of 10 to 500 characters." / "A close exception is approved by a user other than the requester."
  • "The job is not under close review." / "A reason of 3 to 500 characters is required." / "Only a financially closed job is reopened."

Holds:

  • "Hold type or scope is not valid." / "Reopen the job before placing a hold." / "Hold reason is required."
  • "Hold was not found." / "This hold is already released." / "Release reason is required."

Role differences

  • Operations coordinator: sees Cargo, Milestones, House bill, Holds, Operations and the Request financial close button, but not Estimated margin (no operations.job_costing.manage) and not the close decision forms.
  • Operations manager and Company administrator: also see Estimated margin and Approve for billing.
  • Finance manager: sees the close decision forms (Approve financial close, Close as approved exception, Return for remediation, Reopen financial close) but no milestone, measure or hold controls.
  • A user with finance.job.close but without finance.job.close_exception sees Exception reason and Close as approved exception disabled, with "Clear the findings, or ask a user who may approve a close exception."
  • Documentation officer and Warehouse or dispatch user: read-only overview (they lack operations.job.complete and operations.milestone.record).
  • AR accountant: 403 on /jobs/{id} (no operations.job.view).

Job section tabs

Below the header of every job page there is a row of section tabs (the job tabs partial) (aria label Job sections, أقسام العمل): Overview (نظرة عامة) /jobs/{id}, Air (جوي) /jobs/{id}/air, Routing legs (مراحل المسار) /jobs/{id}/legs, Tasks (المهام) /jobs/{id}/tasks, Customs (الجمارك) /jobs/{id}/customs, Custody (الحيازة) /jobs/{id}/custody, Documents (المستندات) /jobs/{id}/documents, Release (الإفراج) /jobs/{id}/release, Communication (المراسلات) /jobs/{id}/communication, Purchase orders (أوامر الشراء) /jobs/{id}/purchase-orders, Claims (المطالبات) /jobs/{id}/claims.

Job section tabs

All tabs are shown to everyone who can open the job, for every mode. A tab the user may not open returns 403. The Air tab also opens for an ocean or road job; its forms then refuse with "Air waybills are only for air jobs." or "Flights are only booked for air jobs." Air is described in chapter 6; Customs, Custody, Documents and Release in chapter 7.

Routing legs

Open: job page > Routing legs tab. Route: /jobs/{id}/legs. Needs operations.shipment.view. This is the job legs screen. Page title Routing legs (مراحل المسار).

Picture not taken (05-job-legs). The demo has no multimodal job. The reason is in the screenshot index.

Legs run in sequence. A leg departs only after the previous leg has arrived, unless an approved exception covers that leg. The Legs table has #, Mode, Route, Provider, Reference, Status and Exceptions. Leg statuses: planned, departed, arrived. The Exceptions column lists exception approvals with their status and "(used)" once consumed. The Leg events timeline lists departures, arrivals and estimates.

The first leg moves with the mode's own records (ocean milestones, flights, trips). Its Departed and Arrived buttons are still shown but the server refuses them.

Picture not taken (05-legs-add-form). The demo has no multimodal job. The reason is in the screenshot index.

Fields

Depart / arrive / exception (row controls):

Field Required What to enter / rules
Departed at (UTC) Yes Departure date and time, UTC, not in the future.
evidence (placeholder "Evidence") Yes Evidence of departure or arrival.
Arrived at (UTC) Yes Arrival date and time, UTC, not before departure.
reason (placeholder "Why depart before the previous leg arrives") Yes Reason for the leg exception.

Set estimate:

Field Required What to enter / rules
Estimated departure (UTC) One of the two UTC date and time.
Estimated arrival (UTC) One of the two Not before the estimated departure.
Estimate source Yes Carrier schedule, Agent advice or Staff estimate.

Add leg:

Field Required What to enter / rules
Mode Yes Ocean, Air, Road, Rail or Barge.
Origin code Yes Location code of 3 to 10 letters or digits.
Destination code Yes Location code of 3 to 10 letters or digits; must differ from the origin.
Carrier reference No Up to 128 characters.
Service scope No Up to 128 characters.

Actions

Button/link What it does Permission Confirmation Result
Departed /legs/{id}/depart operations.milestone.record None Leg departed
Depart under exception /legs/{id}/depart with the approved exception operations.milestone.record None Leg departed; approval marked used
Request exception /legs/{id}/exception (legs 2 and later) operations.leg.manage None Approval request (operations.leg_exception), decided in Workspace > Approvals by a user with operations.exception.approve
Arrived /legs/{id}/arrive operations.milestone.record None Leg arrived
Set estimate /legs/{id}/estimate operations.milestone.record None Estimate shown under the row
Add leg /jobs/{id}/legs operations.leg.manage None New planned leg at the end

The routes are documented together as /legs/{id}/(exception|depart|arrive).

Rules and messages

  • "Origin and destination must differ." / "Legs are added to open jobs only." / "Provider is not valid." / "… must be a location code of 3 to 10 letters or digits."
  • "Enter an estimated departure or arrival." / "Estimated arrival cannot be before estimated departure."
  • "The first leg moves with its flights, trips or ocean milestones."
  • "This leg has already departed." / "Only a departed leg can arrive." / "Arrival cannot be before departure."
  • "The previous leg has not arrived. An approved exception is needed to start this leg."
  • "The previous leg has arrived. No exception is needed."
  • Approval consumption: "The exception is not approved.", "The approval expired. Ask for a new one.", "The approval is for a different action or record." A leg exception approval expires after 72 hours.

Role differences

  • Without operations.leg.manage: no Add leg panel and no Request exception.
  • Without operations.milestone.record: no Departed, Arrived or Set estimate controls.
  • The requester cannot decide their own exception: "The requester cannot decide their own request."

Job tasks

Open: job page > Tasks tab. Route: /jobs/{id}/tasks. Needs operations.task.view. Page title Job tasks (مهام العمل).

Job tasks and the add-task form

Milestones create tasks from the matching templates. Overdue open tasks raise an alert for the owner. The Tasks table lists the job's tasks (status open, done or cancelled); opening a task leads to /tasks/{id} (chapter 2).

Add a task form

Fields

Add a task:

Field Required What to enter / rules
Title Yes Up to 255 characters.
Owner No "Me, or the team below" by default; otherwise an active internal user of this company.
Team No Team name, up to 64 characters.
Due (UTC) No Due date and time in UTC.
Priority No Normal or High.
Depends on task No Another task of this job that must be done first.
Description No Up to 1000 characters.
Checklist No One item per line; start with ? for optional. At most 30 items.

Actions

Button/link What it does Permission Confirmation Result
Add task /jobs/{id}/tasks operations.task.manage None Task open; you are taken to /tasks/{id}

Rules and messages

  • "A task can have at most 30 checklist items."
  • "The task it depends on was not found."
  • "The owner must be an active internal user of this company."

Role differences

  • The add form is shown to everyone who can open the tab. Users without operations.task.manage get 403 on submit.

Task templates

Open: sidebar Workspace > Tasks, then the Templates button. Route: /task-templates. Page title Task templates (قوالب المهام). Viewing needs operations.task.view.

Task templates

When a milestone is recorded, every active template matching its code, mode and direction creates one task. Company templates come first; global defaults also apply. The Templates table has Title, Scope (Global default or Company), Mode, Direction, Milestone, Due after, Team, Checklist and Status. Active company templates have a Retire button.

Fields

Add a company template:

Field Required What to enter / rules
Mode Yes Any, Ocean, Air or Road.
Direction Yes Any, Export, Import, Domestic or Cross trade.
Milestone code Yes A milestone code: 3 to 64 lower-case letters and underscores, e.g. arrived.
Due after Yes Hours after the milestone, 0 to 1440 (default 24).
Title Yes Task title.
Team No Team that owns the task.
Checklist No One item per line; start with ? for optional.

Actions

Button/link What it does Permission Confirmation Result
Add template /task-templates operations.task.configure None Active company template
Retire /task-templates/{id}/retire operations.task.configure "Retire this template? It will no longer create tasks." (danger) Template retired

Rules and messages

  • "Trigger must be a milestone code."
  • "Due offset must be between 0 and 1440 hours."
  • "Only a company template can be retired. Global defaults are replaced by adding company templates."

Role differences

  • The add form and Retire are shown to every viewer; only users with operations.task.configure (Operations manager, Company administrator) can save. Others get 403.

Job communication

Open: job page > Communication tab. Route: /jobs/{id}/communication. Needs operations.job.view and operations.communication.send. Page title Job communication (مراسلات العمل).

Job communication with compose and preview

Messages are built from approved templates using shipment facts only; costs, margins and internal notes cannot be placed in a template. Messages are queued and sent by the scheduled job. No mail provider is configured, so queued messages stay queued and none is reported as delivered.

Compose builds a preview. Preview (template version {version}) shows the subject and body with the shipment facts filled in, and Queue message puts it in the outbox. The Messages table has Subject, To, Queued, Template, Status, Attempts and Detail. Message statuses: queued (waiting for the scheduled job), provider_accepted (a provider took the message), failed (gave up after the maximum attempts), cancelled.

Fields

Compose:

Field Required What to enter / rules
Template Yes Template key and language, e.g. "shipment_update (en) default".
Recipient name Yes Name used in the greeting.
Recipient email Yes A valid email address.
Message No Free text placed in the {{message}} placeholder.

Cancel a queued message:

Field Required What to enter / rules
reason (placeholder "Reason", aria "Cancel reason") Yes Why the message is cancelled.

Actions

Button/link What it does Permission Confirmation Result
Preview /jobs/{id}/communication/preview operations.job.view, operations.communication.send None Preview panel; nothing is queued
Queue message /jobs/{id}/communication/send operations.communication.send None Message queued; a repeated submit of the same preview returns the same message
Cancel /messages/{id}/cancel operations.communication.send "Cancel this queued message?" (danger) Message cancelled
Manage templates Opens /email-templates operations.communication.send None Email templates page

Rules and messages

  • "Recipient email is not valid."
  • "Email template was not found."
  • "Message was not found." / "Only a queued message can be cancelled."
  • Delivery is done by cron with retries after 5, 15, 60 and 240 minutes, at most 5 attempts (Developer-assisted to observe). Without a provider profile messages remain queued.

Role differences

  • Coordinator and manager roles have operations.communication.send. Finance manager can open the job but gets 403 on this tab.

Email templates

Open: job page > Communication tab > Manage templates. Route: /email-templates. Viewing needs operations.communication.send. Page title Email templates (قوالب البريد الإلكتروني).

Email templates and allowed placeholders

Saving a template creates a new version; messages keep the version they were built from. The Allowed placeholders notice lists the only placeholders a template may use: recipient_name, sender_name, shipment_reference, job_reference, origin, destination, status, message. Any other placeholder is refused, so costs, margins and internal notes cannot be sent. The Templates table has Key, Language, Scope, Version, Subject and Body. The seeded global templates (shipment_update, document_request) exist in English only.

Fields

Save a company template:

Field Required What to enter / rules
Key Yes Lower case letters, digits and underscores, starting with a letter, 3 to 64 characters.
Language Yes English or Arabic.
Subject Yes Up to 255 characters.
Body Yes Up to 10000 characters; placeholders as {{name}}.

Actions

Button/link What it does Permission Confirmation Result
Save new version /email-templates operations.communication.configure None New active company version; the previous version is retired

Rules and messages

  • "Template key must be lower case letters, digits and underscores."
  • "Placeholder {{x}} is not allowed. Allowed: …"

Role differences

  • The save form is shown to every viewer. Only Operations manager and Company administrator (operations.communication.configure) can save; others get 403.

Purchase orders

Open: job page > Purchase orders tab. Route: /jobs/{id}/purchase-orders. Needs operations.commitment.view. Page title Purchase orders (أوامر الشراء).

Purchase orders of a job

A purchase order commits cost to a supplier. It counts as committed cost once approved by someone other than the requester. It posts nothing to the ledger; the supplier's bill does. Each order shows Supplier, Total, the Supplier reference once confirmed, its lines and an approval timeline. Statuses:

  • draft – created, not yet submitted.
  • submitted – waiting for an approval in Workspace > Approvals (decided by operations.commitment.approve).
  • approved – approval applied; the cost is committed.
  • confirmed – the supplier confirmed it.
  • cancelled – cancelled with a reason.

Raise a purchase order form

Fields

Raise a purchase order:

Field Required What to enter / rules
Supplier Yes Active supplier of this company.
Currency Yes Active currency.
Charge Yes for a used line Active buy charge (lines[...][charge_code_id], Line 1 to 3).
Description (lines[...][description_text]) Yes for a used line What is bought.
Quantity (lines[...][quantity]) Yes for a used line Decimal greater than zero.
Unit (lines[...][unit_code]) Yes for a used line Unit of measure.
Unit rate (lines[...][unit_rate]) Yes for a used line Decimal, not negative.

Row controls:

Field Required What to enter / rules
reason (placeholder "Why this commitment") Yes Reason for the commitment, sent with the approval request.
supplier_reference (placeholder "Supplier confirmation") Yes The supplier's confirmation reference.
reason (placeholder "Cancel reason") Yes Why the order is cancelled.

Actions

Button/link What it does Permission Confirmation Result
Create draft order /jobs/{id}/purchase-orders operations.commitment.manage None Order draft
Submit for approval /purchase-orders/{id}/submit operations.commitment.manage None Order submitted; approval request created
Apply approval /purchase-orders/{id}/activate operations.commitment.manage None Order approved when the approval was approved by another user
Supplier confirmed /purchase-orders/{id}/confirm operations.commitment.manage None Order confirmed
Cancel /purchase-orders/{id}/cancel operations.commitment.manage "Cancel this purchase order?" (danger) Order cancelled

The routes are documented together as /purchase-orders/{id}/(submit|activate|confirm|cancel).

Rules and messages

  • "Add at least one line." / "Supplier and currency must be active records in this company." / "Each line needs an active buy charge and a unit."
  • Line checks: "Quantity on line n must be …", "Description on line n is required."
  • "Only a draft purchase order can be submitted." / "Submit the purchase order for approval first." / "Only an approved purchase order can be confirmed." / "This purchase order is already …" (cancelled or closed).
  • Approval: "The exception is not approved." (not yet decided), "The approval expired. Ask for a new one." (168 hours), "The requester cannot decide their own request."

Role differences

  • Without operations.commitment.manage the create form and row buttons are hidden.

Claims and insurance

Open: job page > Claims tab. Route: /jobs/{id}/claims. Needs operations.claim.view. Page title Claims and insurance (المطالبات والتأمين).

Claims and insurance of a job

Reserves and settlements need finance approval and post no ledger entry here. Claim effect on this job shows, per currency, Reserved, Settled, Recovered and Net (a management figure). Each claim (reference prefix CLM) shows its type, the party, incident time, amounts, policy and an event timeline. Claim statuses:

  • open – recorded; a reserve or settlement may be requested.
  • reserved – an approved reserve was applied.
  • settled – an approved settlement was applied; recoveries can be recorded.
  • closed – closed after settlement.
  • rejected – rejected from open or reserved (a rejected claim is closed).

Open a claim form

Fields

Claim footer forms:

Field Required What to enter / rules
Note Yes A note on the claim (Add note).
Request Yes Reserve or Settlement.
Amount Yes Decimal greater than zero, in the claim currency.
Reason Yes Why this amount.
Rejection note Yes Why the claim is rejected.
Recovered amount Yes Amount recovered (settled claims only).
From whom Yes Who paid the recovery.
Closing note Yes Closing remark.

Open a claim:

Field Required What to enter / rules
Type Yes Damage, Shortage, Delay or Other.
Direction Yes Received from customer or Filed against provider.
Claimant Yes Who claims.
Counterparty No Against whom.
Incident (UTC) Yes When it happened.
Claimed Yes Claimed amount, decimal.
Currency Yes Valid currency code, e.g. USD.
Description Yes What happened.
Insurance No None, or an insurance reference of this job.

Insurance references:

Field Required What to enter / rules
Insurer Yes Insurer name.
Policy number Yes Policy number.
Certificate number No Certificate number.
Insured value Yes Decimal.
Currency Yes Valid currency code.
Valid from No Cover start date.
Valid to No Cover end date, not before Valid from.
Coverage No What the cover includes.

Actions

Button/link What it does Permission Confirmation Result
Open claim /jobs/{id}/claims operations.claim.manage None Claim open
Add insurance /jobs/{id}/insurance operations.claim.manage None Insurance reference listed
Add note /claims/{id}/note operations.claim.manage None Note in the timeline
Request approval /claims/{id}/request operations.claim.manage None Approval request (finance.claim_reserve or finance.claim_settlement), decided by finance.claim.approve in Approvals
Apply approval #{id} /claims/{id}/apply operations.claim.manage "Apply this approved amount to the claim?" Claim reserved or settled
Record recovery /claims/{id}/recovery operations.claim.manage None Recovered amount increases
Reject /claims/{id}/close (outcome rejected) operations.claim.manage "Reject this claim? A rejected claim is closed." (danger) Claim rejected
Close /claims/{id}/close (outcome closed) operations.claim.manage "Close this claim?" Claim closed

The routes are documented together as /jobs/{id}/(claims|insurance) and /claims/{id}/(note|request|apply|recovery|close).

Rules and messages

  • "Currency is not valid." / "Insurance reference is not on this job." / "Cover ends before it starts."
  • "This claim is …" (wrong status for the action) / "Approval request was not found." / "Recoveries are recorded on settled claims."
  • "Only a settled claim is closed." / "Only an open or reserved claim is rejected."
  • Approval consumption: "The exception is not approved.", "The approval expired. Ask for a new one.", "The situation changed after the approval. Ask for a new approval of the current details."

Role differences

  • Operations coordinator and Finance manager have operations.claim.view only: they see claims but no forms.
  • Operations manager and Company administrator manage claims. The Finance manager decides the approval requests.

Procedures

Record the milestones of an ocean export job

  1. Open Operations > Jobs and shipments and click the job reference.
  2. In Milestones, click Apply milestone plan if the list is empty.
  3. For the next pending milestone, enter Occurred (UTC) and Evidence for {milestone}, then click Record milestone.
  4. Repeat for each milestone in order.
  5. At departed, enter Pieces, Occurred (UTC) and Departure evidence, then click Record departure quantity. Enter the full lot to record the milestone.
  6. Open Tasks to see tasks created by task templates for the recorded milestones.

Measure cargo

  1. On the job page, in Cargo, type the Measured quantity for the line.
  2. Click Record measurement.
  3. If a warning says the quantity differs from booked, tell the person who manages charges; no charge revision is created.

Place and release a hold

  1. In Holds, choose Hold type and Hold scope, type the Hold reason, click Place hold.
  2. To lift it, type the Release reason next to the hold and click Release hold, then confirm "Release this hold?".

Add a sell estimate and approve it for billing (maker-checker)

  1. Signed in as the Operations manager, in Estimated margin, choose Sell charge, type Quantity, choose Unit, type Unit rate, choose Currency, click Record sell estimate.
  2. Sign in as a different user with operations.sell_line.approve and click Approve for billing on the line; confirm.
  3. A user with finance.invoice.draft (for example browser-staff) chooses Tax and clicks Create invoice draft from {n} approved line(s).

Complete operations and request financial close

  1. Release every active hold and make sure every leg of a multimodal job has arrived.
  2. In Operations, click Complete operations and confirm.
  3. In Financial close, review the findings and click Request financial close; confirm.
  4. A Finance manager opens the job, reviews the findings and clicks Approve financial close (no findings) or types an Exception reason of at least 10 characters and clicks Close as approved exception. The approver must not be the requester.
  5. To send it back, the Finance manager types a Return reason and clicks Return for remediation.

Reopen operations

  1. If finance is under review, ask finance to return or reopen it first.
  2. In Operations, type the Reopen reason and click Reopen; confirm.

Add a leg and depart it early under an exception

  1. Open Routing legs. In Add leg, choose Mode, type Origin code and Destination code, optionally Carrier reference and Service scope, click Add leg.
  2. On leg 2 or later, type the reason and click Request exception.
  3. A user with operations.exception.approve approves it in Workspace > Approvals.
  4. Back on Routing legs, enter Departed at (UTC) and the evidence and click Depart under exception.

Send a shipment update

  1. Open Communication. Choose the Template, type Recipient name, Recipient email and optionally Message.
  2. Click Preview and check the text.
  3. Click Queue message. The message appears as queued.

Raise and approve a purchase order

  1. Open Purchase orders. Choose Supplier and Currency, fill at least Line 1, click Create draft order.
  2. Type the reason and click Submit for approval.
  3. Another user with operations.commitment.approve approves it in Workspace > Approvals.
  4. Click Apply approval, then when the supplier confirms type the reference and click Supplier confirmed.

Handle a claim

  1. Open Claims. Fill Open a claim and click Open claim.
  2. Choose Request = Reserve, type Amount and Reason, click Request approval.
  3. A Finance manager approves it in Workspace > Approvals.
  4. Click Apply approval #{id} and confirm. Repeat with Settlement.
  5. On a settled claim, record recoveries, then type a Closing note and click Close.

Troubleshooting

Symptom Cause What to do
"No milestone template is configured for this mode and direction." Ocean or air domestic/cross-trade jobs have no plan Record progress with legs or tasks; ask an administrator about a plan (Developer-assisted)
"Record earlier milestones first." Milestones must be recorded in order Record the earlier pending milestone first
"A partial departure does not record the whole lot as departed." Fewer pieces than the lot were entered at departed Record the remaining pieces when they leave
"Occurrence time cannot be in the future." The time was entered in local time ahead of UTC, or in the future Enter the time in UTC
"An active hold blocks operational completion." An active hold exists Release the hold with a reason, then complete
"Every leg of a multimodal job must arrive before operational completion." A leg is not arrived Record arrival on Routing legs
"Financial close is under review; finance must return or reopen it first." Reopen of operations while finance reviews Ask finance to Return for remediation
"The job has open findings: …" Findings exist and an ordinary approve was posted Clear the findings or close as an approved exception
"A close exception is approved by a user other than the requester." Same user requested and approved A different finance user approves
"The person who entered a sell estimate cannot approve it." Maker-checker Another user approves
"There are no approved, unbilled sell lines on this job." Nothing approved yet Approve sell lines first
"A draft cannot be released. Original-document and financial holds are separate, and this draft clears neither." Request release is always refused for a draft Issue the bill on the Documents tab and release from the Release tab (chapter 7)
"The first leg moves with its flights, trips or ocean milestones." Departed/Arrived used on leg 1 Record leg 1 through milestones, flights or trip stops
"The previous leg has not arrived. An approved exception is needed to start this leg." Leg ordering Record the previous arrival, or request an exception
Messages stay queued No mail provider is configured Expected; ask an administrator about the provider profile (Developer-assisted)
"Placeholder {{x}} is not allowed. Allowed: …" Template uses a placeholder outside the list Use only the allowed placeholders
403 on Save new version or Add template Missing configure permission Ask an Operations manager
419 page after submitting a form The form token expired (page open too long or session renewed) Reload the page and submit again

6. Air, road and consolidated movements

This chapter covers the mode-specific work on a job and the shared movements that carry several jobs: the Air tab of a job (waybills, flights, bookings, screening and dangerous goods), the master air waybill page, consolidations (lots, declared pieces, carrier estimates, cost splits, approvals and accrual) and road trips (stops, cargo assignment, dispatch, proof of delivery, failed delivery and redelivery). Ocean jobs have no separate tab: ocean progress is recorded with the ocean milestone plan and the house bill on the job overview (chapter 5), and the house bill is issued from the Documents tab (chapter 7).

Who uses it:

Role What the role does here Permission keys
Operations coordinator Drafts air waybills, adds flights and bookings, opens consolidations, assigns lots, records carrier estimates and proposes splits, plans and works road trips operations.air.view, operations.air.manage, operations.consolidation.view, operations.consolidation.manage, operations.trip.view, operations.trip.manage, operations.milestone.record
Operations manager All of the above, plus issuing and cancelling waybills, screening and dangerous-goods reviews, approving carrier estimates and split revisions, overriding trip capacity adds operations.air.issue, operations.handling.review, operations.cost_estimate.approve, operations.trip.override
Documentation officer Reads the Air tab operations.air.view
Warehouse or dispatch user Reads trips operations.trip.view
Company administrator Consolidations including accrual operations.consolidation.manage, operations.cost_estimate.approve, finance.accrual.post
AP accountant, Finance manager Hold finance.accrual.post but no operations.consolidation.view, so they cannot open the consolidation page (see Role differences) finance.accrual.post

Spec references: §8 (ocean), §9 (air), §10 (road and multimodal), §14.2 (allocation). UML: 11, 17, 55. Acceptance: A06 (air chargeable weight), A07 (shared master cost split), A08 (shipment split over two flights).

Air (job tab)

Open: job page > Air tab (جوي). Route: /jobs/{id}/air. Needs operations.air.view. Page title Air; a Dangerous goods badge appears in the header when the shipment is flagged.

Air tab of an air export job

Air tab with numbered areas

Numbered areas: 1 summary, 2 Air waybill, 3 Flights and lot bookings, 4 Screening and dangerous goods, 5 Flight events.

Summary. Shipment, Movement, Dangerous goods (yes/no), Lot pieces, Booked on open flights, Departed, Arrived. A part shipment is not marked departed or arrived until every piece has moved.

Air waybill (بوليصة الشحن الجوي). If no air chargeable-weight policy is configured, a warning says a pricing manager must set one before a waybill can be drafted, and the draft form is hidden. The table has AWB, Type (direct, house, master), Master, Pieces, Gross kg, Volume kg, Chargeable kg, Status and Action. Waybill statuses: draft (editable record, not sent to the airline), issued (fixed; a fingerprint is shown), cancelled. Chargeable weight is calculated with the company's air policy. The draft form appears when the shipment has no active direct waybill.

Flights and lot bookings (الرحلات الجوية وحجوزات الدفعات). One row per booking: Flight, Date, Route, Booked, Departed, Arrived, Offloaded, Missing, Damaged, Status. Booking statuses: booked, partially_departed, departed, partially_arrived, arrived, offloaded, cancelled. Under each booking an event form records departure, arrival, offload, missing or damaged pieces. Departure and arrival events also record the job's departed and destination milestones when the earlier milestones are recorded and the whole lot has moved.

Screening and dangerous goods (الفحص الأمني والبضائع الخطرة). A timeline of reviews (security screening or dangerous goods; outcome passed or failed). Export departure needs a passed security screening; dangerous goods also need a passed review. A passed security screening records the security_screened milestone when it is next in sequence. The button Flag as dangerous goods / Clear dangerous goods flag switches the flag.

Flight events. Occurred, Flight, Event, Pieces, Evidence.

Draft a waybill form

Air tab in Arabic

Fields

Draft a waybill:

Field Required What to enter / rules
Type Yes Direct or House under a master.
Master (house only) For a house An open master air waybill of a consolidation that contains this shipment.
AWB number Direct: yes Direct: 11 digits (format 123-45678901) whose last digit is the 7-digit serial modulo 7. House: 3 to 20 letters, digits or dashes.
Airline No Airline name.
Origin Yes 3-letter airport code, e.g. CAI.
Destination Yes 3-letter airport code, different from the origin.
Pieces Yes Whole number of pieces.
Gross weight Yes Kilograms, decimal greater than zero.
Dimensions Dimensions or volume Count x L x W x H cm, one per line, up to 50 lines. The counts must add up to the pieces.
Or volume Dimensions or volume Cubic metres.
Terms Yes Prepaid or Collect.
Shipper Yes Shipper text.
Consignee Yes Consignee text.
Nature of goods Yes Goods description.
Currency In practice yes A valid currency code, e.g. USD. The field is not marked required, but an empty or unknown code is refused with "Currency is not valid."

Waybill row controls:

Field Required What to enter / rules
reason (placeholder "Cancel reason") Yes Why the waybill is cancelled.

Flight event (per booking):

Field Required What to enter / rules
Event Yes Departed, Arrived, Offloaded, Missing or Damaged.
Pieces Yes Decimal greater than zero, within the pieces that can take that event.
Occurred (UTC) Yes UTC, not in the future; an arrival not before the flight departed.
Evidence Yes Evidence of the event (manifest, NOTOC, carrier message).

Book pieces on a flight:

Field Required What to enter / rules
Flight Yes A scheduled flight.
Pieces Yes Not more than the lot that is not yet booked.
Rebooked from No An earlier booking whose offloaded pieces are rebooked.

Add a flight:

Field Required What to enter / rules
Carrier Yes Carrier code, e.g. MS.
Flight number Yes Flight number, e.g. 985.
Flight date Yes Date.
Origin Yes 3-letter airport code.
Destination Yes 3-letter airport code, different from the origin.
Scheduled departure (local) No Local date and time at the origin.
Departure time zone No IANA zone, e.g. Africa/Cairo.
Scheduled arrival (local) No Local date and time at the destination.
Arrival time zone No IANA zone, e.g. Europe/Berlin.

Record a review:

Field Required What to enter / rules
Review type Yes Security screening or Dangerous goods.
Outcome Yes Passed or Failed.
Method Yes How it was checked (x-ray, ETD, document check).
Reviewed at (UTC) Yes UTC, not in the future.
UN number Dangerous goods Format UN1234.
Hazard class Dangerous goods For example 3 or 2.1.
Packing group Dangerous goods I, II or III.
Evidence Yes Reference of the screening record or DG declaration.
I hold current DG training Dangerous goods Tick to confirm current training.

Actions

Button/link What it does Permission Confirmation Result
Draft waybill /jobs/{id}/air/waybills operations.air.manage None Waybill draft with chargeable weight
Issue /air/waybills/{id}/issue operations.air.issue "Issue this waybill? An issued waybill is not edited." Waybill issued with fingerprint
Cancel /air/waybills/{id}/cancel operations.air.issue "Cancel this waybill?" (danger) Waybill cancelled
Book pieces on flight /jobs/{id}/air/bookings operations.air.manage None Booking booked
Add flight /air/flights operations.air.manage None Flight available in the Flight list
Record event /air/bookings/{id}/events operations.milestone.record None Booking quantities and status updated; event listed
Flag as dangerous goods / Clear dangerous goods flag /jobs/{id}/air/dangerous-goods operations.handling.review None Flag on or off
Record review /jobs/{id}/air/reviews operations.handling.review None Review in the timeline

The waybill routes are documented together as /air/waybills/{id}/(issue|cancel|reconcile).

Rules and messages

Waybills:

  • "Air waybills are only for air jobs." / "Choose an open master air waybill." / "Add this shipment to the master consolidation before issuing a house waybill under it." / "This shipment already has an active air waybill. Cancel it before creating another."
  • "An airline waybill number has 11 digits…" / "The waybill check digit does not match (the 7-digit serial modulo 7)."
  • "Currency is not valid."
  • Issue: "Only a draft waybill can be issued." / "Correct the validation errors before issuing." / "Dangerous goods need a passed review by a trained person before issue."
  • Cancel: "This waybill is already cancelled." / "Cancel the house waybills under this master first."

Flights and bookings:

  • "Enter a carrier code and flight number, for example MS 777." / "Origin and destination airports must differ."
  • "Flights are only booked for air jobs." / "Pieces can only be booked on a scheduled flight." / "Booked pieces would exceed the lot. …" / "Only offloaded pieces of this shipment can be rebooked."
  • Events: "Flight booking was not found." / "Only … pieces …" (more pieces than can take the event) / "Only pieces that have not departed can be offloaded. …" / "Arrival cannot be before the flight departed." / "Only departed pieces that have not arrived can be reported missing." / "Only arrived pieces can be reported damaged." / "Export air cargo needs a passed security screening before departure."

Reviews:

  • "A dangerous-goods review needs the reviewer to confirm current training."
  • "Enter the UN number (UN1234) and hazard class (for example 3 or 2.1)." / "Packing group must be I, II or III."
  • "A dangerous-goods review is recorded. The flag stays on." (clearing the flag after a review)

Role differences

  • Operations coordinator: sees Issue and Cancel (they are shown without a permission check) but gets 403 "You do not have permission for this action." because they need operations.air.issue.
  • Operations coordinator: sees Flag as dangerous goods (shown with operations.air.manage) but the service needs operations.handling.review; result 403. The Record review form is shown only with operations.handling.review.
  • Documentation officer: read-only (no draft, booking or flight forms).
  • The flight event form is shown to operations.air.manage; posting needs operations.milestone.record (both are in the coordinator and manager roles).

Master air waybill

Open: consolidation page > Master air waybill panel > the AWB number link. Route: /air/masters/{id}. Needs operations.air.view. Page title Master AWB {number}.

Picture not taken (06-air-master). The demo has no air master. The reason is in the screenshot index.

The summary shows Consolidation, Route, Status, Reconciliation, Master pieces, Gross weight, Chargeable weight, Houses (count, pieces and kg) and Pieces assigned to the consolidation. When the master and its houses differ, a warning "Master and houses do not reconcile:" lists the differences. House waybills (البوالص الفرعية) lists House AWB, Shipment, Pieces, Gross kg, Chargeable kg, Status.

Reconciliation statuses: empty (not reconciled yet), reconciled (master totals match the houses), discrepancy_accepted (differences accepted with a note). A master is issued only after one of these two.

Fields

Field Required What to enter / rules
Reconciliation note Only when totals differ Up to 500 characters. With a note, a difference is accepted; without one it is refused.

Actions

Button/link What it does Permission Confirmation Result
Record reconciliation /air/waybills/{id}/reconcile operations.air.manage None Reconciliation reconciled or discrepancy_accepted
Issue master /air/waybills/{id}/issue operations.air.issue "Issue this master waybill? An issued waybill is not edited." Master issued

Rules and messages

  • "Master air waybill was not found."
  • "Only a master waybill is reconciled." / "Only a draft master is reconciled."
  • "Master totals differ from the houses: … Accept the discrepancy with a note or correct the waybills."
  • "Reconcile the master with its houses before issuing it."

Role differences

  • The reconciliation form and Issue master are shown to every viewer; users without operations.air.manage (reconcile) or operations.air.issue (issue) get 403.

Consolidations

Open: sidebar Operations > Consolidations (التجميعات), or the Consolidations button on the jobs list. Route: /consolidations. Needs operations.consolidation.view.

Consolidations list and the open form

The operating company is the signed-in company. A consolidation holds shipment lots. It does not allocate carrier cost and it does not post finance. The table has Reference, Mode, Master reference and Status. Consolidation statuses: open (declared pieces match the assigned lots, or nothing declared) and review (declared pieces differ from the assigned lots and wait for review). Each road trip opens its own road consolidation with the trip reference as master reference.

Fields

Open consolidation:

Field Required What to enter / rules
Branch Yes Active branch.
Mode Yes Ocean, Air or Road.
Master reference No Carrier or master reference text. It is not a master bill.

Actions

Button/link What it does Permission Confirmation Result
Open consolidation /consolidations operations.consolidation.manage None New consolidation open
Reference link Opens /consolidations/{id} operations.consolidation.view None Consolidation detail

Rules and messages

  • "Master reference is too long." / "Choose an active branch." / "Mode is not valid."

Role differences

  • Without operations.consolidation.manage the open form is hidden and the list takes the full width.

Consolidation detail

Open: a reference on the consolidations list, or the Consolidation link on a trip or master AWB. Route: /consolidations/{id}. Page title is the consolidation reference.

Consolidation detail

Consolidation detail with numbered areas

Numbered areas: 1 summary, 2 Assigned lots, 3 Carrier cost estimate, 4 Proposed split, 5 Master air waybill.

The subtitle states the rules: the master reference is not a master bill; a carrier amount is an estimate until it is accrued; a split by pieces, gross weight or chargeable weight is a proposal until approved; approving the split freezes it; approving an estimate needs an approved split, a different approver from the person who recorded the estimate, and declared pieces that match; accrual posts the approved amount to freight expense and accrued freight payable and does not create a supplier bill or customer invoice; assigned pieces, weight and volume cannot exceed the lot that remains.

Summary. Mode, Status, Master reference, Active pieces, Active volume, Declared pieces. In status review a warning says "The declared pieces do not match the assigned lots. The difference is waiting for review. The carrier estimate was not rewritten."

Assigned lots (الدفعات المخصصة). Job, Shipment, Pieces, Volume, Status (active or released). Releasing a lot does not allocate cost.

Carrier cost estimate (تقدير تكلفة الناقل) (shown with operations.consolidation.manage). Charge, Payee, Quantity, Rate, Amount, Status; revisions show "(revision n)" and adjustments "Adjustment of line {id}: {reason}". Estimate statuses:

  • estimated – recorded, not approved; can be split, revised and approved.
  • approved – approved against an approved split; can be accrued.
  • accrued – posted to freight expense and accrued freight payable.
  • matched – matched to a supplier bill (chapter 9).
  • superseded – replaced by a revision.

Proposed split (التوزيع المقترح). Each line is one lot's share of the estimate, split by the chosen basis: pieces, gross weight or chargeable weight. The shares add up to that estimate once. For each revision the panel shows "Revision {n} for {charge}", the status, Basis and Denominator, and per lot Job, Lot, Basis, Amount, Exact share and Residue (rounding residue). Split statuses: proposed, approved (frozen), superseded. A stale proposal (lots changed) shows "The lots have changed since this proposal. It was not rewritten. Propose the split again before approving it."; a stale approved split says to use Revise split. A revision of an accrued split says "This revises the accrued split of batch {id}. Approving it posts an accrual adjustment that moves cost between the jobs; the total stays the same."

Master air waybill (بوليصة الشحن الجوي الرئيسية) (air consolidations only). Lists the master with status and reconciliation; without one, the Draft master AWB form is shown.

Carrier cost estimate and the record form

Proposed split panel

Fields

Assign lot and declare total:

Field Required What to enter / rules
Job id Yes Numeric id of a job of the same mode with a shipment lot.
Pieces Yes Decimal greater than zero, not more than the lot that remains.
Declared pieces Yes Decimal, not negative.

Record estimate:

Field Required What to enter / rules
Charge Yes Active buy charge.
Payee Yes Active partner profile of this company.
Quantity Yes Decimal greater than zero.
Unit Yes Unit of measure.
Rate Yes Decimal greater than zero.
Currency Yes Active currency. Accrual needs the company base currency.

Estimate row controls:

Field Required What to enter / rules
Split basis Yes By pieces, By gross weight or By chargeable weight.
quantity (aria "Revised quantity") Yes New quantity.
unit_rate (aria "Revised rate") Yes New rate.
reason (placeholder "Reason") Yes Why the estimate changes.
reason (placeholder "Why the split changes") Yes Why the split is revised.

Draft master AWB:

Field Required What to enter / rules
Master AWB number Yes 11 digits with a valid check digit, e.g. 123-45678901.
Airline No Airline name.
Origin airport Yes 3-letter code.
Destination airport Yes 3-letter code.
Pieces Yes Whole pieces.
Gross weight Yes Kilograms.
Volume No Cubic metres.
Freight terms No Prepaid or Collect.
Nature of goods Yes For example "Consolidation as per manifest".
Shipper Yes Usually the company itself.
Consignee Yes Usually the destination agent.

Actions

Button/link What it does Permission Confirmation Result
Assign lot /consolidations/{id}/lots operations.consolidation.manage None Lot active; status recalculated
Declare total /consolidations/{id}/declare operations.consolidation.manage None Status open if it matches, review if not
Release /allocations/{id}/release operations.consolidation.manage "Release this lot from the consolidation?" Lot released
Record estimate /consolidations/{id}/estimates operations.consolidation.manage None Estimate estimated
Propose split /buy-charges/{id}/propose operations.consolidation.manage None Split proposed
Approve allocation /allocation-batches/{id}/approve operations.consolidation.manage "Approve this allocation? Approving freezes the split." Split approved
Approve estimate /buy-charges/{id}/approve operations.cost_estimate.approve "Approve this carrier estimate?" Estimate approved
Accrue cost /buy-charges/{id}/accrue finance.accrual.post "Accrue this cost? It posts to freight expense and accrued freight payable." Estimate accrued; journal posted
Revise estimate /buy-charges/{id}/revise operations.consolidation.manage None Before accrual: new estimate revision, old one superseded. After accrual: an increase becomes a separate adjustment line
Revise split /buy-charges/{id}/revise-split operations.consolidation.manage None Approved (not accrued): estimate back to estimated for a new split. Accrued: a proposed split revision
Approve split revision /allocation-batches/{id}/approve-revision operations.cost_estimate.approve "Approve this split revision? It posts an accrual adjustment." Accrual adjustment posted between jobs
Draft master AWB /consolidations/{id}/master-awb operations.air.manage None Master draft
AWB link Opens /air/masters/{id} operations.air.view None Master page

Staging only: Accrue cost and Approve split revision post journals. Run them on staging, and use a consolidation nobody else is testing.

Rules and messages

Lots and declaration:

  • "Assigned pieces must be a decimal." / "Assigned pieces must be greater than zero." / "Job was not found." / "The job mode does not match the consolidation." / "The job has no shipment." / "The shipment has no lot and leg to assign." / "The assignment is larger than the remaining lot."
  • "Declared pieces must be a decimal." / "Declared pieces cannot be negative."
  • "Allocation was not found." / "This assignment is already released."

Estimates and splits:

  • "Quantity and rate must be decimals." / "Quantity and rate must be greater than zero." / "Charge, unit, currency, and payee must be active records in this company."
  • "Split basis is not valid." / "Only an estimate can be split, and the split stays unapproved." / "Assign at least one lot before proposing a split." / "An approved split stays as it is. Use Revise split to propose a new version." / "A proposed split already matches these lots."
  • "A lot has no gross weight, so the split cannot use weight." / "Every lot needs a house air waybill with chargeable weight before the split can use it."
  • "Allocation proposal was not found." / "Only a proposed split can be approved." / "The lots changed after this split was proposed. Propose the split again before approving it." / "The stored split does not match its denominator. It was not approved."
  • Approve estimate: "Only an estimated cost can be approved." / "The person who recorded this estimate cannot approve it." / "Declared pieces must match the assigned lots before a cost is approved." / "Approve the split before approving the estimate." / "The lots changed after the split was approved. Revise the split before this estimate can be approved."
  • Accrue: "This carrier cost is already accrued." / "Only an approved carrier estimate can be accrued." / "Accrual needs the company base currency. No exchange rate is configured." / "Declared pieces must match the assigned lots before this cost is accrued." / "The lots changed after the split was approved. Revise the split before accruing."
  • Revise: "The revision does not change the estimate." / "The revision does not change the accrued cost." / "This cost is already accrued. Record the lower amount on the supplier bill; matching posts the variance, or ask the supplier for a credit note." / "This cost line was already superseded. Revise the newest revision."
  • Revise split: "Only an approved or accrued cost has a split to revise. Propose a split for an estimate instead." / "The approved split still matches the lots. Nothing to revise."
  • Approve split revision: "Split revision was not found." / "Only a proposed split revision can be approved." / "The cost changed after this revision was proposed. Revise the split again." / "The person who proposed this split cannot approve it." / "The lots changed again after this revision was proposed. Revise the split again." / "The revised split does not total the accrued cost. Nothing was posted."
  • "An estimate is not a posted payable and it does not create an invoice."

Master AWB:

  • "Consolidation was not found." / "A master air waybill needs an air consolidation." / "This consolidation already has an active master air waybill."

Role differences

  • Users without operations.consolidation.manage see only the summary, assigned lots and master AWB; the estimate and split panels are hidden.
  • Approve estimate and Approve split revision appear only with operations.cost_estimate.approve (Operations manager, Company administrator). Approve allocation needs only operations.consolidation.manage.
  • Accrue cost appears only to a user who has both operations.consolidation.manage (to see the panel) and finance.accrual.post, for example the Company administrator or browser-staff. AP accountant and Finance manager hold finance.accrual.post but get 403 on the page.

Road trips

Open: sidebar Operations > Road trips (الرحلات البرية). Route: /trips. Needs operations.trip.view.

Road trips list and the plan form

Each trip carries its own road consolidation, so shared trip costs split across the jobs on it. The Trips table has Trip, Service (FTL or LTL), Vehicle, Planned start, Stops, Jobs and Status. Trip statuses: planned (stops and cargo can be added), dispatched (stops are worked), completed (all cargo delivered).

Plan a trip form

Fields

Plan a trip:

Field Required What to enter / rules
Branch Yes Active branch.
Service Yes Full truckload or Less than truckload.
Vehicle type Yes Up to 64 characters, e.g. 40ft trailer.
Plate No Vehicle plate, up to 64 characters.
Border No No or Yes.
Haulier No "Own fleet or not yet chosen", or a partner of this company.
Capacity weight Yes Kilograms, decimal.
Capacity volume Yes Cubic metres, decimal.
Planned start (UTC) Yes Date and time in UTC.
Driver No Up to 128 characters.
Driver phone No Up to 32 characters.
Permits No Up to 500 characters.

Actions

Button/link What it does Permission Confirmation Result
Plan trip /trips operations.trip.manage None Trip planned with reference TRP… and its road consolidation
Trip link Opens /trips/{id} operations.trip.view None Trip detail

Rules and messages

  • "Haulier is not valid."
  • Field length and decimal checks name the field, e.g. "Weight capacity …", "Planned start …".

Role differences

  • Without operations.trip.manage the Plan a trip panel is hidden (Warehouse or dispatch user sees the list only).

Trip detail

Open: a trip on the trips list. Route: /trips/{id}. Page title Trip {ref} (الرحلة {ref}).

Trip detail with stops and jobs

Trip detail with numbered areas

Numbered areas: 1 header buttons, 2 summary, 3 Stops, 4 Jobs on this trip.

Header buttons: Dispatch trip (planned trips) and Complete trip (dispatched trips). Summary: Service, Vehicle, Haulier ("own fleet" when none), Status, Consolidation (link), Weight and Volume ("{load} of {capacity}"), Pieces. The consolidation holds the shared trip costs.

Stops (المحطات): #, Type (pickup, delivery, border; "(redelivery)" for a redelivery stop), Location, Window, Status, Arrived, Completed, Detail (POD reference and signer, or failure reason). Stop statuses: pending, arrived, completed, failed. Stop controls appear only while the trip is dispatched.

  • Loading a pickup stop records the departure of the assigned pieces on each job (road departed milestone when the whole lot leaves).
  • Delivering a delivery stop records the arrival and the delivered milestone, with "POD {reference}" as evidence.
  • A failed delivery keeps the cargo loaded (assignment status failed) and creates a high-priority task "Arrange redelivery: {trip} stop {n}", due in 4 hours, for the trip owner. Scheduling a redelivery adds a new delivery stop at the same place, moves the cargo to it and marks that task done.

Jobs on this trip (الأعمال على هذه الرحلة): Job, Pieces, Weight kg, Pickup stop, Delivery stop, Status (assignment statuses planned, loaded, delivered, failed). Capacity overrides lists every over-capacity assignment with its reason, measure and user.

Trip detail on a phone

Fields

Add stop:

Field Required What to enter / rules
Stop type Yes Pickup, Delivery or Border.
Location code Yes Up to 64 characters; stored in capitals.
Address No Up to 500 characters.
Window from (UTC) No UTC date and time.
Window to (UTC) No Not before Window from.

Assign cargo:

Field Required What to enter / rules
Road job Yes A road job of this company.
Pieces Yes Decimal greater than zero, within the lot that remains.
Pickup stop Yes A pickup stop of this trip.
Delivery stop Yes A delivery stop with a later sequence.
Capacity override reason Only if over capacity Needed when the load would exceed weight or volume capacity.

Stop controls (dispatched trip):

Field Required What to enter / rules
Arrived at (UTC) Yes Arrival at the stop, UTC, not in the future.
Loaded at (UTC) Yes Loading time at a pickup stop.
evidence (placeholder "Evidence") Yes Loading evidence.
Delivered at (UTC) Yes Delivery time.
pod_reference (placeholder "POD reference") Yes Proof of delivery reference, up to 255 characters.
pod_signed_by (placeholder "Signed by") Yes Who signed the POD, up to 128 characters.
Failed at (UTC) Yes Attempt time.
reason (placeholder "Failure reason") Yes Why the delivery failed.
window_from (aria "Window from (UTC)") Yes Redelivery window start (failed stops).
window_to (aria "Window to (UTC)") Yes Redelivery window end.

Actions

Button/link What it does Permission Confirmation Result
Add stop /trips/{id}/stops operations.trip.manage None Stop pending at the end
Assign /trips/{id}/assign operations.trip.manage; over capacity also operations.trip.override None Assignment planned; lot added to the trip consolidation
Dispatch trip /trips/{id}/dispatch operations.trip.manage "Dispatch this trip?" Trip dispatched
Arrived /trip-stops/{id}/arrive operations.milestone.record None Stop arrived
Loaded /trip-stops/{id}/pickup operations.milestone.record None Stop completed; assignments loaded; departure recorded on the jobs
Delivered /trip-stops/{id}/deliver operations.milestone.record None Stop completed with POD; assignments delivered; arrival recorded
Failed /trip-stops/{id}/fail operations.milestone.record "Record this delivery as failed?" (danger) Stop failed; redelivery task created
Schedule redelivery /trip-stops/{id}/redeliver operations.trip.manage None New delivery stop "(redelivery)"; cargo moved; task done
Complete trip /trips/{id}/complete operations.trip.manage "Complete this trip?" Trip completed

The routes are documented together as /trips/{id}/(stops|assign|dispatch|complete) and /trip-stops/{id}/(arrive|pickup|deliver|fail|redeliver).

Rules and messages

  • "Trip was not found." / "Stop was not found." / "The stop is not on this trip."
  • "The time window ends before it starts." / "Stops are added to planned or dispatched trips only."
  • "Cargo is assigned while the trip is planned." / "Choose a pickup stop and a later delivery stop of this trip." / "Only road jobs go on a trip."
  • "This load exceeds the trip weight capacity (… planned, … available). Give an override reason to proceed." (also for volume) / "You cannot override trip capacity."
  • "Only a planned trip is dispatched." / "Assign cargo before dispatching the trip."
  • "Stops are worked on a dispatched trip." / "Arrival is recorded once, at a pending stop." / "Only an open pickup stop is completed."
  • "Delivery needs a proof of delivery reference." / "Only an open delivery stop is completed." / "Load every lot for this stop before delivering it."
  • "Only an open delivery stop can fail." / "Only a failed delivery is redelivered." / "A redelivery stop already exists for this failure."
  • "Only a dispatched trip is completed." / "Some cargo on this trip is not delivered yet."

Role differences

  • Warehouse or dispatch user (operations.trip.view only): read-only trip page, no stop controls.
  • Operations coordinator: can plan, assign, dispatch and work stops, but an over-capacity assignment with a reason is refused with "You cannot override trip capacity." Operations manager can override.

Procedures

Draft, check and issue a direct air waybill

  1. Open the air job, tab Air. If the policy warning shows, ask a pricing manager to set the air chargeable-weight policy.
  2. In Draft a waybill choose Type Direct, type the AWB number (11 digits with check digit), Origin, Destination, Pieces, Gross weight and either Dimensions or Or volume, then Terms, Shipper, Consignee, Nature of goods and Currency.
  3. Click Draft waybill. Check Chargeable kg in the table.
  4. For an export, record a passed security screening (see next procedure).
  5. As an Operations manager click Issue and confirm.

Record screening and a dangerous-goods review

  1. As an Operations manager open the Air tab.
  2. If the cargo is dangerous goods, click Flag as dangerous goods.
  3. In the review form choose Review type Security screening, Outcome Passed, type Method, Reviewed at (UTC) and Evidence, click Record review.
  4. For dangerous goods record a second review with UN number, Hazard class, Packing group, and tick I hold current DG training.

Book a shipment on two flights (split shipment)

  1. Under Add a flight, add each flight (Carrier, Flight number, Flight date, Origin, Destination) and click Add flight.
  2. Choose the first Flight, type part of the pieces, click Book pieces on flight; repeat with the second flight for the rest.
  3. When each flight leaves, use Event Departed with Pieces, Occurred (UTC) and Evidence, click Record event.
  4. The job shows departed only when every piece has departed; arrival likewise.

Build a consolidation and split a shared carrier cost

  1. Open Operations > Consolidations, choose Branch and Mode, click Open consolidation.
  2. On the detail page enter Job id and Pieces and click Assign lot for each job.
  3. Enter Declared pieces equal to the total and click Declare total. Status must be open.
  4. In Carrier cost estimate choose Charge, Payee, type Quantity, choose Unit, type Rate, choose Currency, click Record estimate.
  5. Choose the Split basis and click Propose split. Check that the shares add up to the estimate.
  6. Click Approve allocation and confirm.
  7. Sign in as a different user with operations.cost_estimate.approve and click Approve estimate.
  8. A user with finance.accrual.post and access to the page clicks Accrue cost (staging only).

Correct a carrier cost after approval

  1. Open Revise on the estimate.
  2. Before accrual: type the new quantity and rate and a reason, click Revise estimate, then propose and approve a new split and approve again.
  3. After accrual: an increase creates an adjustment line; a decrease is refused and is handled on the supplier bill or a credit note.
  4. If lots changed after approval, type the reason and click Revise split; for an accrued cost, a different approver clicks Approve split revision.

Plan and run a road trip

  1. Open Operations > Road trips, fill Plan a trip and click Plan trip.
  2. On the trip, add a Pickup stop and a later Delivery stop with Add stop.
  3. Choose Road job, type Pieces, choose Pickup stop and Delivery stop, click Assign.
  4. Click Dispatch trip and confirm.
  5. At the pickup: Arrived, then Loaded with time and evidence.
  6. At the delivery: Delivered with time, POD reference and signer.
  7. Click Complete trip and confirm.

Handle a failed delivery

  1. On the delivery stop type Failed at (UTC) and the reason, click Failed and confirm.
  2. Open Workspace > Tasks to see "Arrange redelivery: …".
  3. On the failed stop enter the new window and click Schedule redelivery. Deliver the new stop as usual.

Troubleshooting

Symptom Cause What to do
No draft form on the Air tab, warning about the policy No air chargeable-weight policy A pricing manager sets the policy
"The waybill check digit does not match (the 7-digit serial modulo 7)." AWB number mistyped Check the 11-digit number with the airline
"Correct the validation errors before issuing." The draft has stored validation errors (for example same airports or piece counts not matching dimensions); the page does not list them Cancel the draft and draft it again with correct data
"Export air cargo needs a passed security screening before departure." No passed security review Record a passed screening, then the departure event
403 on Issue, Cancel or Flag as dangerous goods Coordinator lacks operations.air.issue or operations.handling.review Ask an Operations manager
"Reconcile the master with its houses before issuing it." Master not reconciled Record reconciliation (with a note if totals differ)
"Declared pieces must match the assigned lots before a cost is approved." Consolidation in review Correct the lots or declare the right total
"The person who recorded this estimate cannot approve it." Maker-checker A different user approves
"Accrual needs the company base currency. No exchange rate is configured." Estimate in a foreign currency Record the estimate in the base currency
"This load exceeds the trip weight capacity (…). Give an override reason to proceed." Over capacity Give a reason; an Operations manager assigns it
"Load every lot for this stop before delivering it." Pickup not completed Record Loaded at the pickup stop first
"Some cargo on this trip is not delivered yet." Open or failed assignment Deliver or redeliver the remaining cargo
419 page after submit Form token expired Reload and submit again

7. Customs, custody, release and documents

This chapter covers what happens to cargo and papers around the border and the warehouse: customs cases with declaration lines, assessments, status changes and duty or tax payments; warehouse receipts and dispatches from custody; the cargo and document release with its blocking checks and the release exception; and the documents filed on a job, including the house bill of lading issue and the company's private file store.

Who uses it:

Role What the role does here Permission keys
Customs coordinator Opens and works customs cases, requests duty and tax payments operations.customs.view, operations.customs.manage
Operations coordinator Reads customs and custody, records releases that are not blocked, files job documents operations.customs.view, operations.custody.view, operations.release.record, operations.document.view, operations.document.manage, documents.file.upload, documents.file.download
Operations manager Everything above plus custody receipts and dispatches, release exceptions (request and approve), house bill issue and restricted documents adds operations.customs.manage, operations.custody.manage, operations.release.override, operations.release.approve, operations.transport_document.issue, operations.document.restricted
Documentation officer Files job documents, drafts and issues house bills, records releases documents.file.upload, documents.file.download, operations.document.view, operations.document.manage, operations.transport_document.draft, operations.transport_document.issue, operations.release.record
Warehouse or dispatch user Records warehouse receipts and dispatches operations.custody.view, operations.custody.manage
Finance manager Reads customs cases, approves duty and tax payments in Approvals and marks them paid operations.customs.view, finance.disbursement.approve

Spec references: §11.1 (customs), §11.2 (custody), §12 (holds, exceptions and approvals), §13 (documents), §15 (credit control on release). UML: 12, 13, 14, 15, 52. Acceptance: A09 (release with holds), A24 (unsafe upload and unauthorised download), A01 (company isolation).

Customs cases

Open: sidebar Operations > Customs. Route: /customs. Needs operations.customs.view. Page title Customs (الجمارك), heading Customs cases (الحالات الجمركية).

Customs cases list

The subtitle reads "Customs cases across jobs. A held case places a customs hold on the job's cargo release until it is released." The table lists up to 200 cases, open cases first and newest first: Case (reference starting with CUS, links to the job's Customs tab), Job, Direction, Declaration (type), Number (declaration number), Status, Updated. With no cases the page says "No customs cases. Open one from a job's Customs tab."

Case statuses and the moves the code allows:

From Allowed next statuses
draft submitted, cancelled
submitted assessed, inspection, held, released, rejected
assessed inspection, held, released
inspection assessed, held, released
held assessed, inspection, released, rejected
rejected draft
released, cancelled none (final)

Fields

The list has no form.

Actions

Button/link What it does Permission Confirmation Result
Case reference Opens /jobs/{id}/customs operations.customs.view No Job Customs tab

Rules and messages

  • Only cases of your current company are listed.

Role differences

Customs coordinator, Operations coordinator, Operations manager and Finance manager see the list. Roles without operations.customs.view (for example Documentation officer, Warehouse or dispatch user) have no sidebar link and get "You do not have permission for this action." (403).

Job customs (Customs tab)

Open: job page > Customs tab, or a case link on the Customs cases list. Route: /jobs/{id}/customs. Needs operations.customs.view.

Customs tab of a job

Customs tab with numbered areas

Numbered areas: 1 case details, 2 Declaration lines, 3 Assessments, 4 Change status, 5 Duty and tax payments.

Customs tab in Arabic

The subtitle reads "Duty and tax payments are requested here and approved by finance. Recording a payment here posts no ledger entry; the payment is recognised when finance records it." Without a case the tab says "No customs case on this job." and shows Open a customs case (فتح حالة جمركية) to users who may manage customs.

Each case shows its reference and status, then Direction, Declaration type, Declaration, Broker, Office and Released (release time and reference). A held case shows a red Held notice with the hold note.

  • Declaration lines (بنود البيان): #, HS code, Description, Origin, Quantity, Customs value. "No lines." when empty.
  • Assessments (الربوط الجمركية): Type, Amount, Reference, Assessed.
  • Change status (تغيير الحالة): the next statuses allowed for the current status.
  • Duty and tax payments (مدفوعات الرسوم والضرائب): Payee, Purpose, Funding, Amount, Status (requested, paid), Approval (status of the finance approval). "None requested." when empty.
  • History: every status change with time, user and note.

Add a declaration line form

Fields

Open a customs case:

Field Required What to enter / rules
Direction Select Import, Export or Transit.
Declaration type Yes The declaration code, for example IM4. Up to 32 characters.
Broker No Broker name as free text, up to 255 characters.
Customs office No Office code, up to 32 characters.

Declaration lines (shown while the case is draft or rejected):

Field Required What to enter / rules
HS code Yes 6 to 12 digits; spaces and dots are removed before the check.
Description Yes Goods description, up to 500 characters.
Origin country No Two-letter country code, for example EG.
Quantity Yes Decimal greater than zero.
Unit Yes Unit code, for example PCS; up to 32 characters.
Customs value Yes Decimal, zero or more.
Currency Yes A currency code known to the system, for example USD.

Assessments (shown when the case is not draft, released or cancelled):

Field Required What to enter / rules
Assessment type Select Duty, VAT, Excise, Fee or Penalty.
Amount Yes Decimal, zero or more.
Currency Yes Known currency code (placeholder EGP).
Reference No Assessment notice reference, up to 128 characters.
Assessed at (UTC) Yes Date and time in UTC; not in the future.

Change status:

Field Required What to enter / rules
New status Select Only the allowed next statuses are offered.
Declaration number When submitting Required to submit unless the case already has one; up to 64 characters. On release it becomes the release reference if given.
Occurred at (UTC) Yes When it happened, in UTC; not in the future.
Note For hold, rejection or cancellation Help text: "Required for hold, rejection or cancellation." Up to 500 characters.

Duty and tax payments, request form (shown unless the case is cancelled):

Field Required What to enter / rules
Payee Yes Who is paid (customs authority, port); up to 255 characters.
Purpose Yes What the payment is for; up to 255 characters. Used as the approval reason.
Funding Select Customer prefunded or Company advance.
Amount Yes Decimal greater than zero.
Currency Yes Known currency code.

Mark paid (in a payment row):

Field Required What to enter / rules
payment_reference (placeholder "Payment reference") Yes The bank or receipt reference of the payment; up to 128 characters.

Actions

Button/link What it does Permission Confirmation Result
Open case Posts /jobs/{id}/customs operations.customs.manage No Case draft with reference CUS…; history "Case opened."
Add line Posts /customs/{id}/lines operations.customs.manage No New numbered declaration line
Record assessment Posts /customs/{id}/assessments operations.customs.manage No Assessment row added
Apply Posts /customs/{id}/transition operations.customs.manage "Apply this customs status change?" Status changed, history entry; held adds a customs cargo hold on the job; released lifts it
Request payment Posts /customs/{id}/disbursements operations.customs.manage No Payment requested with a finance approval request
Mark paid Posts /disbursements/{id}/paid finance.disbursement.approve "Mark this payment as paid?" Payment paid; the approval is used; no journal is posted

All four case forms post to /customs/{id}/(lines|assessments|transition|disbursements). The approval for a payment (valid 168 hours) is decided on the Approvals page (/approvals) by a user with finance.disbursement.approve; the requester cannot decide their own request. Mark paid appears only when the payment is requested and its approval is approved.

Rules and messages

  • "HS code must have 6 to 12 digits."
  • "Origin country must be a 2-letter code."
  • "Currency is not valid."
  • "Declaration lines change only while the case is a draft." (lines are also accepted while the case is rejected).
  • "Assessments are recorded after the declaration is submitted." (refused in draft and cancelled).
  • "A customs case cannot move from submitted to draft." (the text names the actual statuses).
  • "Enter the declaration number when submitting."
  • "Add at least one declaration line before submitting."
  • "Note is required." when holding, rejecting or cancelling without a note.
  • "Disbursements follow a submitted declaration." (no payment request in draft or cancelled).
  • "Only a requested disbursement is paid." and "Disbursement was not found."
  • "Occurrence time cannot be in the future." / "Assessment time cannot be in the future."
  • A held case adds a job hold of type customs on cargo with the reason "Customs {reference}: {note}". It blocks the cargo release (see Release). Moving the case to released releases those holds.
  • Marking a payment paid posts no ledger entry. How the payment reaches the accounts is an open question (docs/OPEN_QUESTIONS.md); do not expect a journal.
  • Every change is audited; refused status changes are audited as failures.

Role differences

  • Customs coordinator and Operations manager see all forms.
  • Operations coordinator and Finance manager see the tab read-only; Finance manager also sees Mark paid on approved payments.
  • Without operations.customs.view the tab returns 403.

Cargo custody

Open: sidebar Operations > Cargo custody. Route: /custody. Needs operations.custody.view. Page title Cargo custody (عهدة البضائع), heading Warehouse receipts (إيصالات المستودع).

Cargo custody list

The subtitle reads "Warehouse receipts across jobs. Cargo leaves custody only against a recorded cargo release." The table shows Receipt (WR… reference, links to the job's Custody tab), Job, Warehouse, Condition and Received. With no receipts: "No cargo received yet. Record receipts from a job's Custody tab."

Fields

The list has no form.

Actions

Button/link What it does Permission Confirmation Result
Receipt reference Opens /jobs/{id}/custody operations.custody.view No Job Custody tab

Rules and messages

  • Only receipts of your current company are listed.

Role differences

Warehouse or dispatch user, Operations coordinator and Operations manager see the list. Other roles get 403.

Job custody (Custody tab)

Open: job page > Custody tab (الحيازة). Route: /jobs/{id}/custody. Needs operations.custody.view. Page title Custody.

Custody tab of a job

Three figures at the top: Received (with "{n} damaged"), Dispatched and On hand ("Pieces in custody").

  • Receipts (المقبوضات): one card per receipt with its reference, a condition badge (good, damaged, discrepancy), Warehouse / Location, Received, Delivered by, Exception note, and the lines Description, Marks, Pieces, Damaged, Weight kg, Volume m³. "No cargo received." when empty.
  • Record a warehouse receipt (تسجيل إيصال مستودع): a folded section, open it to record a receipt.
  • Dispatches (عمليات الإخراج): Dispatch (DSP… reference), Pieces, Collected by, Vehicle, Released to, Dispatched. "Nothing dispatched." when empty. If the job has no cargo release, the page says "Cargo cannot leave custody until a cargo release is recorded on the Release tab." with a link to /jobs/{id}/release?scope=cargo, and the dispatch form is hidden.

Record a warehouse receipt form

Fields

Record a warehouse receipt:

Field Required What to enter / rules
Warehouse Yes Warehouse or facility name, up to 128 characters.
Location No Bay or location in the warehouse, up to 128 characters.
Delivered by No Who brought the cargo, up to 255 characters.
Received (UTC) Yes When the cargo arrived, in UTC; not in the future.
Condition Select Good, Damaged or Discrepancy.
Exception note Unless Good Help text: "Required unless the condition is good." Up to 500 characters.

Lines (three fieldsets, Line 1 to Line 3; a line counts when it has a description or pieces):

Field Required What to enter / rules
Description Yes for a used line Goods description, up to 255 characters.
Marks No Marks and numbers, up to 255 characters.
Pieces Yes for a used line Decimal greater than zero.
Damaged No Damaged pieces; not more than Pieces. Any damaged pieces need Condition Damaged.
Weight No Kilograms, decimal.
Volume No Cubic metres, decimal.

Dispatch (shown when a cargo release exists):

Field Required What to enter / rules
Release Yes (select) One of the job's cargo releases, shown as "Release to {recipient} ({date})".
Pieces Yes Decimal greater than zero, not more than on hand.
Collected by Yes Who collected the cargo, up to 255 characters.
Vehicle No Vehicle plate, up to 64 characters.
Dispatched at (UTC) Yes When it left, in UTC; not in the future.

Actions

Button/link What it does Permission Confirmation Result
Record receipt Posts /jobs/{id}/custody/receipts operations.custody.manage No Receipt WR… with its lines; Received and On hand go up
Dispatch Posts /jobs/{id}/custody/dispatches operations.custody.manage "Dispatch these pieces from custody?" Dispatch DSP… recorded; On hand goes down
Release tab link Opens /jobs/{id}/release?scope=cargo operations.release.record No Release page

Rules and messages

  • "Add at least one received line."
  • "Damaged pieces on line 1 cannot exceed the pieces received." (the line number varies).
  • "Set the condition to damaged when damaged pieces are recorded."
  • "Exception note is required." when the condition is not Good and the note is empty.
  • "Received time cannot be in the future." / "Dispatch time cannot be in the future."
  • "Record a cargo release for this job before dispatching from custody."
  • "Only 4 pieces are in custody." (the number is the current on-hand quantity).
  • Receipts and dispatches are audited (custody.received, custody.dispatched) and are not edited afterwards.

Role differences

  • Warehouse or dispatch user and Operations manager record receipts and dispatches.
  • Operations coordinator sees the tab read-only (no receipt section, no dispatch form).

Release (Release tab)

Open: job page > Release tab (الإفراج), or the link on the Custody tab. Route: /jobs/{id}/release, with ?scope=cargo (default) or ?scope=documents. Needs operations.release.record.

Picture not taken (07-job-release). The demo has no job held for release. The reason is in the screenshot index.

Picture not taken (07-job-release-annotated). The demo has no job held for release. The reason is in the screenshot index.

Numbered areas: 1 Cargo / Documents scope buttons, 2 Blocking items with the exception notice or form, 3 Record the release form (only when allowed), 4 Releases recorded.

Picture not taken (07-job-release-ar). The demo has no job held for release. The reason is in the screenshot index.

The subtitle reads "Release checks are recalculated when you record the release. Unpaid posted invoices, a customer over its credit limit, active holds and a missing surrendered original bill all block it. An exception must be approved by someone else for exactly the blocks shown, and is used once." A badge shows blocked or ready. The Cargo and Documents buttons switch the scope.

Blocking items (العناصر المانعة) lists each block with its kind:

Kind When it appears
Hold type (for example customs) An active job hold on all scopes or on this scope. A held customs case creates a cargo hold.
document Cargo scope only: the house bill was issued as a paper original and a required category is not filed, for example "Original bill of lading surrender for {house bill reference} is not filed on the job".
unpaid_invoice A posted or credited invoice on the job still has an open amount: "Invoice {number} has {currency} {amount} unpaid".
credit The customer is over its credit limit: "Customer is over its credit limit ({currency} {limit}, available {amount})".

With no blocks: "Nothing blocks the cargo release." (or documents).

When a release exception was approved, a notice says either "Exception #{id} was approved for exactly these blocks and can be used." or "Exception #{id} was approved, but the blocks have changed since. Request a new exception."

Releases recorded (الإفراجات المسجلة): Released to, Scope, Evidence, By, At, Exception.

Picture not taken (07-release-form). The demo has no job ready to release. The reason is in the screenshot index.

Fields

Request release exception (shown when blocked and the user has operations.release.override):

Field Required What to enter / rules
Exception reason Yes Placeholder "Why release despite these blocks". Up to 500 characters.

Record the {scope} release (shown when nothing blocks, or when an approved exception matches the blocks):

Field Required What to enter / rules
Released to Yes Person or company that receives the cargo or documents, up to 255 characters.
Evidence Yes Help text: "Who authorised it and how identity was checked". Up to 500 characters.

Actions

Button/link What it does Permission Confirmation Result
Cargo / Documents Opens /jobs/{id}/release?scope=cargo or ?scope=documents operations.release.record No Checks for that scope
Request release exception Posts /jobs/{id}/release/exception operations.release.override No Approval request for exactly the current blocks; valid 24 hours; decided by a different user with operations.release.approve
Record release Posts /jobs/{id}/release operations.release.record "Record this release? It cannot be undone." Release recorded; documents scope marks the job's documentation as released; an outbox event operations.cargo_released is written
Release under exception Posts /jobs/{id}/release with the approval operations.release.record and operations.release.override "Record this release? It cannot be undone." Release recorded with the exception; the approval is used and cannot be used again

Rules and messages

  • The blocks are recalculated inside the release transaction. If something new blocks it: "Release is blocked: {list}. Clear the holds or use an approved release exception."
  • "Nothing is holding this release, so no exception is needed."
  • "Releasing despite holds needs the release override permission." (403) when a user without operations.release.override submits the exception release.
  • An exception matches only the exact set of blocks. The unpaid-invoice block includes the open amount, so a part payment after approval changes the blocks and the exception no longer matches.
  • The requester of an exception cannot decide it ("The requester cannot decide their own request.").
  • Refused releases are audited as release.refused; recorded releases as release.recorded or release.recorded_with_exception.

Role differences

  • Operations coordinator and Documentation officer record releases that are not blocked. When blocked they see "You cannot request a release exception. Ask an operations manager."
  • Operations manager requests exceptions, approves other people's exceptions (Approvals page) and releases under an exception.
  • Warehouse or dispatch user has no operations.release.record and gets 403 on this tab.

Job documents (Documents tab)

Open: job page > Documents tab (المستندات). Route: /jobs/{id}/documents. Needs operations.document.view (and operations.job.view for the house bill panel). Page title Job documents (مستندات العمل).

Job documents tab

Job documents with numbered areas

Numbered areas: 1 Registered documents, 2 Register a document, 3 House bill of lading.

The subtitle explains that each upload is checked before it is stored, that a new version keeps earlier versions, that restricted documents are shown only to users allowed to see them, and that nothing is published to the portal unless it is published explicitly. When the house bill was issued as a paper original and required categories are missing, a warning says "The house bill was issued as a paper original. Cargo release stays blocked until these are filed: {list}."

Registered documents: Title, Category, Confidentiality (Internal, or a lock tag Restricted), Issue / expiry, Versions (each version vN links to /documents/{id}/download when the file is available, otherwise shows the file name and its status; below it "by {name}", the date, "SHA-256" with the first 12 characters of the fingerprint and the version note), Status, New version. "No documents registered." when empty. Restricted documents are not listed for users without operations.document.restricted.

Register a document form

House bill of lading (بوليصة الشحن الفرعية): without a house bill the panel says "No house bill on this shipment. Open a draft from the job overview." (link to the job overview, chapter 5). Otherwise it shows Reference, Status, Revision and, once issued, Issued as, Place of issue, Issued, Originals, Copies and Fingerprint. A draft bill shows the issue form with the note "Issuing fixes the latest revision. After that the bill is not edited; a correction needs a new document." Drafting and revising the house bill happen on the job overview (/jobs/{id}/house-bill, /transport-documents/{id}/revise, chapter 5).

Picture not taken (07-house-bill-issue-form). The demo has no draft house bill to issue. The reason is in the screenshot index.

Fields

Register a document (upload with a progress bar):

Field Required What to enter / rules
Category Yes (select) A document category, for example Commercial invoice, Packing list, Certificate of origin, Customs declaration, Original bill of lading surrender, Proof of delivery.
Title Yes Document title, up to 255 characters.
Confidentiality Select (default Internal) Internal or Restricted. Restricted needs operations.document.restricted. The list always starts at Internal, so choose Restricted yourself for sensitive categories (customs declaration, original bill surrender, dangerous goods, claim evidence); the category's own default is used only when no value is sent.
Issue date No Date on the document.
Expiry date No Not before the issue date.
File Yes The file to upload. Disallowed types are quarantined and refused here.

New version (in each document row):

Field Required What to enter / rules
upload (aria label "File") Yes The new file.
note (placeholder "What changed", aria label "Version note") No What changed in this version.

Issue the house bill (draft only):

Field Required What to enter / rules
Instrument form Select Paper original, or Non-negotiable (sea waybill). Electronic negotiable bills are not supported.
Place of issue Yes City of issue.
Originals No (default 3) 1 to 3 for paper originals.
Copies No (default 2) 0 to 20.

Actions

Button/link What it does Permission Confirmation Result
Register document Uploads the file and posts /jobs/{id}/documents operations.document.manage and documents.file.upload No Document active with version v1; progress shows "Uploading n%"
Upload version Posts /job-documents/{id}/versions operations.document.manage and documents.file.upload No New version vN; earlier versions kept
vN link Downloads /documents/{id}/download documents.file.download (and operations.document.restricted for restricted documents) No File downloaded as an attachment
Issue bill Posts /transport-documents/{id}/issue operations.transport_document.issue "Issue this house bill? After that the bill is not edited." Bill issued with a fingerprint; a paper original adds the document block on cargo release
Request release (job overview) Posts /transport-documents/{id}/release operations.transport_document.draft — Always refused; the release is recorded on the Release tab (chapter 5)

Rules and messages

  • "Choose a file to upload." (no file chosen).
  • "The file was quarantined because its type is not allowed. It was not filed on the job."
  • "The upload failed. Nothing was saved; try again." (shown by the progress bar when the request fails).
  • "Document category is not valid."
  • "You cannot file restricted documents." (403).
  • "Expiry date cannot be before the issue date."
  • "Document was not found." (also for a restricted document you may not see) and "This document is retired."
  • "Choose paper originals or a non-negotiable waybill. Electronic negotiable bills are not supported."
  • "Paper originals must be between 1 and 3." and "Copies must be between 0 and 20."
  • "Only a draft can be issued. An issued bill cannot be changed." and "The draft has no revision to issue."
  • On the job overview, revise: "Only a draft can be revised." and "The draft has no revision to copy."; request release: "Release the original documents from the job release page, which checks document and finance holds." or "A draft cannot be released. Original-document and financial holds are separate, and this draft clears neither." or "House bill was not found."
  • The file size limit is set by the hosting configuration (default 20 MB). How an oversize upload is reported could not be determined from the code.

Role differences

  • Documentation officer, Operations coordinator and Operations manager register documents and versions.
  • Only Operations manager (with operations.document.restricted) sees and files restricted documents.
  • Documentation officer and Operations manager issue house bills.
  • Roles without operations.document.view (Warehouse or dispatch user, Finance manager) get 403.

Documents (file store)

Open: sidebar Operations > Documents. Route: /documents. Needs documents.file.upload or documents.file.download; otherwise "You do not have permission to view documents." (403). Page title Documents (المستندات).

Documents page

The subtitle reads "Uploads stay in private storage. Disallowed types are quarantined and cannot be downloaded." Files (الملفات) lists the latest 50 files of the company: Name, Status (available, quarantined) and a Download button for available files. "No documents uploaded." when empty. Upload a document (رفع مستند) is the upload form with a progress bar.

Documents page on a phone

Fields

Field Required What to enter / rules
File Yes Any file. Allowed types are set in the hosting configuration; executable and script types are always quarantined.

Actions

Button/link What it does Permission Confirmation Result
Upload Posts /documents documents.file.upload No "The file was stored privately." or "The file was quarantined because its type is not allowed."
Download Opens /documents/{id}/download documents.file.download No File as an attachment; restricted job documents also need operations.document.restricted

Rules and messages

  • "Choose a file to upload." (422).
  • "The file was not found." (404) for a file of another company, a quarantined file, or a restricted document you may not see.
  • "The stored file no longer matches its recorded checksum." (404) when the stored file was changed outside the system.
  • Files are stored outside the web root and are served only through the download route.

Role differences

Documentation officer and Operations coordinator upload and download. Users with only one of the two permissions see the page but the other action is refused with 403.

Procedures

Work a customs case from draft to release

  1. Open the job, click Customs, fill Open a customs case and click Open case.
  2. Add each declaration line with Add line.
  3. In Change status choose submitted, type the Declaration number and Occurred at (UTC), click Apply and confirm.
  4. Record the duty and VAT notices with Record assessment.
  5. If customs holds the cargo, choose held, type the Note and apply. The Release tab now shows a customs block.
  6. When customs releases the goods choose released and apply. The customs hold is lifted.

Pay duty on the customer's behalf

  1. On the Customs tab fill Payee, Purpose, Funding, Amount, Currency and click Request payment.
  2. A finance approver (not the requester) approves it on the Approvals page.
  3. After paying, the Finance manager types the Payment reference and clicks Mark paid. No journal is posted.

Receive cargo into the warehouse

  1. Open the job, click Custody, open Record a warehouse receipt.
  2. Fill Warehouse, Received (UTC), Condition and at least one line; for damage choose Damaged, type the Exception note and the damaged pieces.
  3. Click Record receipt.

Release and dispatch cargo

  1. Open the Release tab with scope Cargo and read Blocking items.
  2. If nothing blocks it, fill Released to and Evidence, click Record release and confirm.
  3. If something blocks it and it must go anyway, an operations manager types the Exception reason and clicks Request release exception. Another operations manager approves it on the Approvals page. Then click Release under exception.
  4. Open Custody, choose the Release, fill Pieces, Collected by and Dispatched at (UTC), click Dispatch and confirm.

File a document and a new version

  1. Open the Documents tab, fill Register a document and click Register document; watch the progress bar.
  2. For a corrected file, choose the file in the document's New version cell, type what changed and click Upload version.

Issue the house bill

  1. Draft the house bill on the job overview (chapter 5).
  2. On the Documents tab choose the Instrument form, type Place of issue, check Originals and Copies, click Issue bill and confirm.
  3. For a paper original, file the surrendered original when it comes back; until then the cargo release shows a document block.

Troubleshooting

Symptom Cause What to do
No Add line form The case is not draft or rejected Lines are fixed after submission; move a rejected case back to draft if it must be corrected.
"Enter the declaration number when submitting." Number empty Type the Declaration number with the submit.
No Mark paid button Payment approval not approved yet, or you lack finance.disbursement.approve Ask a finance approver to decide it on the Approvals page.
Dispatch form missing No cargo release recorded Record the cargo release on the Release tab first.
"Only n pieces are in custody." Dispatch larger than on hand Dispatch at most the on-hand pieces, or record the missing receipt.
Release blocked by a customs hold Customs case is held Move the case to released when customs releases the goods.
"Exception … the blocks have changed since." An invoice was part paid, a hold added or released Request a new exception for the current blocks.
Upload shows "The upload failed. Nothing was saved; try again." Network error or server refusal Check the file type and size and try again.
"The file was quarantined…" Type not allowed Convert to an allowed type (for example PDF) and upload again.
A restricted document is missing from the list You lack operations.document.restricted Ask an operations manager.
"Release the original documents from the job release page…" House bill release is not done on the bill Use the Release tab with scope Documents.

Staging only: release exceptions, marking payments paid and recording releases cannot be undone. Rehearse them on a staging company, not on live jobs.

8. Receivables: invoices, credit notes and receipts

This chapter covers money owed by customers. It explains how to bill a job, post and print the invoice, correct it with a credit note or a debit note, re-bill credited lines, and record customer receipts. It also covers allocating receipts to invoices, moving allocations, applying and refunding customer advances, and reversing receipts.

Who uses it:

Role (exact name) What the role can do here Permission keys
AR accountant Draft, post and print invoices. Draft and post credit and debit notes. Enter, post, reverse and refund receipts. finance.invoice.view, finance.invoice.draft, finance.invoice.post, finance.invoice.print, finance.credit_note.view, finance.credit_note.draft, finance.credit_note.post, finance.receipt.view, finance.receipt.draft, finance.receipt.post
Finance manager Everything the AR accountant can do. Same keys as above
Company administrator View, draft and print. Cannot post anything. finance.invoice.view, finance.invoice.draft, finance.invoice.print, finance.credit_note.view, finance.credit_note.draft, finance.receipt.view, finance.receipt.draft

Executive, Auditor, Treasury user and AP accountant do not hold any finance.invoice.*, finance.credit_note.* or finance.receipt.* key, so these screens are not in their menus. The routes refuse them (see "Role differences" under each screen).

Specification: Spec §15 (customer invoicing and receivables), §17.1 (books and controls), §17.3 (foreign currency) and §21 (reporting). UML: 18 (invoices and correction notes), 35 (financial document posting lifecycle), 37 (open-item settlement), 43 (atomic customer invoice posting) and 45 (receipt allocation and foreign exchange).

How posting works in this area

These rules apply to every screen in this chapter. They describe what the code does.

  • Exact decimals. Amounts are calculated with an exact decimal type, never with floating point. Type amounts with a dot as the decimal separator and no thousands separators ("1250.50", not "1,250.50"). The database stores amounts with 10 decimal places. Rounding is half away from zero. Some amounts are rounded to the currency's minor units (2 for most currencies): tax amounts, debit note charges, and credit note amounts worked out from a quantity. Other amounts are stored exactly as typed, with up to 10 decimal places: receipt amounts, bank charges, allocations, amounts applied from an advance, refunds, and partial credits entered as an amount. More than 10 decimal places is refused. This difference is recorded in the gaps file.
  • Posted documents are not edited. A posted invoice, credit note, debit note, receipt or refund has no edit form. You correct it with a credit note, a debit note, a reversal or a re-allocation. Only drafts can be cancelled.
  • Posting is atomic. In one database transaction, posting reserves the document number and writes the journal. It also updates the open items, the sell lines and the document status, and records the audit event. If any step is refused, nothing is saved and the number is not used. Journal numbers look like JE-2026-000123. Invoice numbers look like {BRANCH}-INV-2026-000001, receipts {BRANCH}-RC-…, credit notes {BRANCH}-CN-… and debit notes {BRANCH}-DN-….
  • Posting date. Invoices, notes and receipts post on the day you post them (UTC). If the current fiscal period is soft-closed or hard-closed, posting is refused with "The fiscal period is soft-closed. Only designated adjustments can post to it." or "The fiscal period is not open for posting." (see chapter 10).
  • Maker-checker as built. A second person is required to post a receipt, reverse a receipt, move a receipt allocation, and post a refund. No second person is required to post an invoice or a customer credit or debit note: any user with the post permission can post a draft they created. No amount thresholds exist. Thresholds are an open business question (docs/OPEN_QUESTIONS.md Q11). Do not assume a limit.
  • Double submission. While a form is being sent, its submit buttons are disabled. The server also refuses a second post of the same document: "Only a draft invoice can be posted. Posted invoices are not edited." or, at journal level, "This source is already posted."
  • Confirmations. Buttons with a confirmation open a dialog titled "Please confirm", with Confirm and Cancel.
  • Refusals. When a button press is refused, the application shows an error page in English. The page shows the status number and a heading, for example "The request could not be completed" or "Not allowed". It also shows the message quoted in this chapter, a Return to the start page button and "Reference …" (a correlation number for support). Use the browser Back button to return to the form.

Creating an invoice draft from a job

Invoices start on the job, not on the invoice list. Open Operations > Jobs and shipments (Arabic: الملفات والشحنات). Then open the job and scroll to the Customer invoices panel (Arabic: فواتير العملاء). The panel lists the job's invoices with Invoice, Status, Total and Date. When the job has approved sell lines that are not yet billed, and you hold finance.invoice.draft, the panel shows a form with a Tax select and the button Create invoice draft from {n} approved line(s). The form posts to /jobs/{id}/invoice-draft.

Picture not taken (08-job-invoice-draft). The AR accountant account was refused on the job, so the Customer invoices panel was not on the page. The reason is in the screenshot index.

Fields

Field Required What to enter / rules
Tax No "No tax", or an active output tax code with its rate. The rate in force today is copied onto the draft lines.

Actions

Button/link What it does Permission Confirmation Result
Create invoice draft from {n} approved line(s) Creates one draft invoice from all approved, unbilled sell lines of the job. The lines are held for this draft. finance.invoice.draft None Opens the new draft at /invoices/{id}.

Rules and messages

  • "There are no approved, unbilled sell lines on this job."
  • "All sell lines on one invoice must share a currency. Approve the lines in one currency per invoice."
  • "The customer profile must be active before it can be invoiced."

Role differences

Users without finance.invoice.draft do not see the form. Approving sell lines is described in the job costing chapter.

Customer invoices list

Open Finance > Invoices (Arabic sidebar label: الفواتير). You can also use the Customer invoices link in Finance > Finance overview. Route: /invoices. Page title: Customer invoices (فواتير العملاء).

Customer invoices list

The list shows the newest invoices first, 100 per page, with a pager at the bottom. The columns are Invoice (the number, or "Draft #id" before posting), Job, Customer, Status, Total and Date. Status badges:

Status Meaning
draft Not posted. No number and no accounting effect. Its sell lines are held.
posted Numbered and in the ledger. Not edited.
credited A posted credit note brought the adjusted total to zero or below.
cancelled A draft that was cancelled. Its sell lines were released.

When there are no invoices, the page says "No customer invoice exists yet. Approve a job sell line, then create a draft from the job page." When a search finds nothing, it says "No invoice matches."

Fields

Field Required What to enter / rules
q (search box, placeholder "Invoice number, job reference or customer") No Part of an invoice number, a job reference or a customer name. Press Search.

Actions

Button/link What it does Permission Confirmation Result
Search Filters the list. finance.invoice.view None The list reloads with matches.
Invoice number link Opens the invoice. finance.invoice.view None /invoices/{id}

Rules and messages

The search is read only. Nothing is changed from this page.

Role differences

Without finance.invoice.view, the sidebar link is hidden, and opening /invoices directly shows "Not allowed" with "Your roles do not include this page. Ask an administrator if you need access."

Invoice detail

Open an invoice from the list, from the job's Customer invoices panel, or from a receipt allocation. Route: /invoices/{id}. Page title: Invoice {number} (Arabic: الفاتورة {number}).

Posted invoice with settlement and correction forms

Draft invoice with Cancel draft and Post invoice

Posted invoice with numbered callouts

Callouts: (1) the PDF (English) and PDF (العربية) buttons; (2) the Settlement totals, ending with Open balance; (3) the Debit note form; (4) the Partial credit form.

Partial credit form

The subtitle explains the status:

  • Draft: "Draft. Its sell lines are held for this invoice. Posting assigns the number and records the revenue."
  • Posted: "Posted on {date}. This invoice is not edited. A credit note can reverse it."
  • Credited: "Credited. The receivable and revenue were reversed by a credit note. This invoice is not edited."
  • Cancelled: "Cancelled. Its sell lines were released."

Panels, top to bottom:

  1. Summary: Number, Status, Customer, Job (link), Invoice date, Currency, Total and, once posted, Journal.
  2. Lines: #, Charge, Quantity, Unit rate, Amount and Credited (how much of the line has been credited). A line billed again after a credit shows "Re-billed from" with a link to the earlier invoice.
  3. Posted journal: Account, Job, Debit and Credit for the posting journal.
  4. Settlement (only for users with finance.receipt.view). This is the settlement view of the invoice. It shows Total, Debit notes and Credit notes (when there are any), Allocated by receipts, and Open balance. Under it, a table lists each receipt that paid the invoice with Receipt, Kind and Amount. The Kind values are "Receipt allocation" (allocated when the receipt was entered), "Applied advance" (applied later from an unapplied balance) and "Moved from another invoice" (moved by a re-allocation).
  5. Credit and debit notes (only for users with finance.credit_note.view): Number, Kind ("Credit note", "Credit note (lines reopened)" or "Debit note"), Status, Total and Reason.
  6. On a draft: an action bar with Cancel draft and Post invoice.
  7. On a posted invoice, for users with finance.credit_note.draft, three forms: Full credit, Debit note and Partial credit.

Header buttons PDF (English) and PDF (العربية) appear for users with finance.invoice.print when the status is not draft or cancelled.

What posting does: posting debits 1100 Accounts receivable for the total. It credits revenue for the net amount: 4100 Freight revenue, or the deferred or unbilled accounts when the company recognises revenue on job completion (see chapter 10). It credits 2500 Output tax for the tax. The sell lines become billed, and commissions on the lines are accrued. Before posting, the customer's credit limit is checked again.

Fields

Full credit form (posts to /invoices/{id}/credit-note with mode full):

Field Required What to enter / rules
Credit reason Yes Why the invoice is credited. At most 255 characters.
Reopen the sell lines so they can be billed again No Tick to return the credited sell lines to "approved", so they can be put on a new invoice (re-billing).

Debit note form (posts to /invoices/{id}/debit-note):

Field Required What to enter / rules
description[] (column "Charge description", 3 rows) First row yes Description of the extra charge, at most 255 characters. Leave unused rows empty.
charge[] (column "Amount", 3 rows) First row yes Positive amount in the invoice currency. Rounded to the currency's minor units.
Reason Yes Why the charges are added. At most 255 characters.

Partial credit form (posts to /invoices/{id}/credit-note with mode partial). Only lines with something left to credit are listed.

Field Required What to enter / rules
line[] (column "Credit", one checkbox per line) At least one Tick each line to credit.
quantity[id] (column "Quantity") One of quantity or amount Quantity to credit. It is credited at the line's unit rate and rounded to minor units. It cannot be more than the line quantity.
credit[id] (column "or amount") One of quantity or amount Amount to credit. It is stored as typed (up to 10 decimal places). It cannot be more than the column "Left to credit".
Credit reason Yes At most 255 characters.
Reopen the credited sell lines for re-billing No Only allowed when every ticked line is credited in full.

The draft invoice action bar has no fields.

Actions

Button/link What it does Permission Confirmation Result
Post invoice (/invoices/{id}/post) Numbers and posts the draft. finance.invoice.post "Post this invoice? It is numbered and cannot be edited afterwards." Status posted, number and journal shown.
Cancel draft (/invoices/{id}/cancel) Cancels the draft and releases its sell lines. finance.invoice.draft "Cancel this draft invoice? Its sell lines will be released." Status cancelled.
Draft full credit note Creates a draft credit note for every line's remaining amount. finance.credit_note.draft "Draft a full credit note for this invoice?" Opens the draft note at /credit-notes/{id}.
Draft debit note Creates a draft debit note with the entered charges. finance.credit_note.draft None (the form warns if you leave with unsaved changes) Opens the draft note.
Draft partial credit note Creates a draft credit note for the ticked lines. finance.credit_note.draft None (unsaved-changes warning) Opens the draft note.
PDF (English) / PDF (العربية) Downloads the invoice PDF (see next screen). finance.invoice.print None PDF file.

Staging only: posting an invoice is irreversible. It can be corrected only with a credit note. Post test invoices only in a test company.

Rules and messages

Posting (/invoices/{id}/post):

  • "Only a draft invoice can be posted. Posted invoices are not edited." (also what a second click or a second tab gets)
  • "A sell line on this draft changed. Cancel the draft and create it again."
  • "The tax rate changed after this invoice was drafted. Cancel the draft and create it again."
  • "This would take the customer over the credit limit ({currency} {limit}, available {available}). Ask for an approved credit override."
  • "No {currency} exchange rate is entered on or before {date}." (foreign-currency invoice with no rate)
  • Period refusals from chapter 10.

Cancelling (/invoices/{id}/cancel): "Only a draft invoice can be cancelled."

Credit notes (/invoices/{id}/credit-note):

  • "A reason is required and must be at most 255 characters."
  • "Only a posted invoice can take a credit or debit note. Drafts are cancelled instead."
  • "This invoice already has a draft note. Post or cancel it first."
  • "This invoice has a receipt allocated. Reverse the receipt or credit part of it." (full credit while a receipt is allocated)
  • "The invoice has no lines to credit." / "Every line of this invoice is already credited."
  • "Choose at least one invoice line to credit." / "Each invoice line can be credited once per note." / "Give a quantity or an amount for each credited line."
  • "Credit quantities must be decimals greater than zero." / "Credit amounts must be decimals greater than zero."
  • "A credited line does not belong to this invoice."
  • "The credited quantity is more than the invoice line." / "The credit is more than this invoice line has left."
  • "Only a line credited in full can be reopened for billing."
  • "The credit is more than the invoice open balance."

Debit notes (/invoices/{id}/debit-note):

  • "A reason is required and must be at most 255 characters."
  • "A debit note needs at least one charge."
  • "Each charge needs a description of at most 255 characters."
  • "Charge amounts must be decimals greater than zero."

Role differences

Missing permission Effect
finance.invoice.post Post invoice is hidden. A direct POST is refused with "Not allowed". Company administrator is in this case.
finance.invoice.draft Cancel draft is hidden.
finance.credit_note.draft The Full credit, Debit note and Partial credit forms are hidden.
finance.receipt.view The Settlement panel is hidden.
finance.credit_note.view The Credit and debit notes panel is hidden.
finance.invoice.print The PDF buttons are hidden.
finance.invoice.view The page shows "Not allowed".

An invoice of another company shows "Not found" with "That page does not exist or is not available in the active company."

Invoice PDF

Click PDF (English) or PDF (العربية) on a posted or credited invoice. Routes: /invoices/{id}/pdf?lang=en and /invoices/{id}/pdf?lang=ar. The file is named {invoice number}-{lang}.pdf.

Invoice PDF in English

Invoice PDF in Arabic

The PDF is built from the template resources/pdf/invoice.php. The English title is "Tax invoice" and the Arabic title is "فاتورة ضريبية". The Arabic copy is laid out right to left. The PDF shows:

  • the company and the header fields Invoice, Date, Job, Customer and Tax ID;
  • a line table with Description, Quantity, Unit rate, Tax and Amount;
  • the totals Net before tax, Tax total and Total due;
  • the company's invoice footer text.

Stored-document reuse. The first request for each invoice and language generates the PDF. The file is saved in private storage with its SHA-256 fingerprint, and the audit event invoice.pdf_generated is recorded. Every later request returns the same stored file and records invoice.pdf_downloaded. The PDF therefore always shows the values as they were when it was first generated after posting. It does not change when a credit note is posted later; the credit note is a separate document.

Quotation PDF (for reference). Quotations have a similar PDF at /quotations/{id}/pdf, built from resources/pdf/quotation.php. Its title is "Quotation" or "عرض سعر". It shows the revision, issue and validity dates, route, commodity, Incoterm, service scope, exclusions, options with lines, totals per currency, a note that no currency conversion was applied, and a lines fingerprint. It needs pricing.quotation.print, and it is also stored and reused per quotation version and language. It is documented in chapter 4.

Quotation PDF for comparison

Fields

Field Required What to enter / rules
lang (in the link) Yes en or ar. Any other value is refused.

Actions

Button/link What it does Permission Confirmation Result
PDF (English) Downloads the English PDF. finance.invoice.print None {number}-en.pdf
PDF (العربية) Downloads the Arabic PDF. finance.invoice.print None {number}-ar.pdf

Rules and messages

  • "Only a posted invoice can be printed." (draft or cancelled invoice opened by URL)
  • "Language must be English or Arabic."
  • "Invoice was not found." (shown as the "Not found" page)

Role differences

Without finance.invoice.print the buttons are hidden, and opening the URL shows "Not allowed". The attempt is recorded in the audit log as a failed invoice.pdf. AR accountant, Finance manager and Company administrator hold the permission.

Credit and debit notes list

Open Finance > Finance overview and click Credit notes in "Finance areas", or open /credit-notes directly. There is no sidebar link. Page title: Credit and debit notes (Arabic: الإشعارات الدائنة والمدينة).

Credit and debit notes list

Columns: Number (or "Draft #id"), Kind (Credit or Debit), Invoice, Customer, Job, Status, Total and Date. Status: draft, posted or cancelled. When empty, the page says "No note exists yet. Open a posted invoice and create a credit or debit note from there." and links to Customer invoices.

Fields

This screen has no form.

Actions

Button/link What it does Permission Confirmation Result
Number link Opens the note. finance.credit_note.view None /credit-notes/{id}
Invoice link Opens the invoice. finance.invoice.view None /invoices/{id}

Rules and messages

Notes are created only from a posted invoice. This page cannot create them.

Role differences

Without finance.credit_note.view the page shows "Not allowed".

Credit note detail

Open a note from the list, from the invoice's Credit and debit notes panel, or right after drafting it. Route: /credit-notes/{id}. Page title: "Credit note {number}" or "Debit note {number}" (Arabic: إشعار دائن / إشعار مدين).

Draft credit note ready to post

Panels: Summary (Number, Status, Invoice, Customer, Job, Date, Currency, Total, Reason), Lines (Charge, Quantity, Amount), Posted journal (after posting), and for a draft an action bar with Cancel draft and Post credit note or Post debit note.

What posting does:

  • A credit note reverses the credited part of the invoice. It debits revenue (or the deferred or unbilled account, following the recognition basis) and output tax, and credits 1100 Accounts receivable. When the invoice's adjusted total reaches zero or less, the invoice status becomes credited. If the note was drafted with a reopen option, the credited sell lines go back to "approved". They then appear again on the job as billable lines, ready for a new draft invoice (re-billing). The new invoice's lines show "Re-billed from".
  • A debit note debits 1100 Accounts receivable and credits revenue. The invoice's open balance grows by the note total.

Fields

This screen has no input fields.

Actions

Button/link What it does Permission Confirmation Result
Post credit note / Post debit note (/credit-notes/{id}/post) Numbers and posts the note. finance.credit_note.post "Post this note? It is numbered and cannot be edited afterwards." Status posted. The invoice settlement is updated.
Cancel draft (/credit-notes/{id}/cancel) Cancels the draft note. The invoice does not change. finance.credit_note.draft "Cancel this draft note? The invoice will not change." Status cancelled.

Staging only: posting a note is irreversible. A wrong credit note can only be offset with a debit note (and the other way round).

Rules and messages

Posting re-checks the invoice, because things may have changed since the draft:

  • "Only a draft note can be posted. Posted notes are not edited." (also the answer to a second click)
  • "The invoice is no longer posted. Cancel this note."
  • "A receipt was allocated to this invoice after the draft. Cancel this credit note."
  • "A note needs at least one line."
  • "A credit line is more than its invoice line has left." (another note credited the line in the meantime)
  • "The note total no longer matches its lines."
  • "The tax rate changed after this debit note was drafted. Cancel it and draft it again."
  • "Only a line credited in full can be reopened for billing."
  • "Credit and debit notes on foreign-currency invoices are not built yet. Correct the invoice by reversal of its receipts and a new invoice."
  • Cancelling: "Only a draft credit note can be cancelled."

Role differences

Without finance.credit_note.post the post button is hidden (Company administrator). Without finance.credit_note.draft Cancel draft is hidden. The person who drafted the note may post it: there is no maker-checker on customer notes.

Customer receipts list

Open Finance > Receipts (Arabic sidebar label: المقبوضات). Route: /receipts. Page title: Customer receipts (سندات قبض العملاء).

Customer receipts list

Customer receipts list on a phone (390 px)

Columns: Receipt (number or "Draft #id"), Customer, Reference, Status, Amount and Receipt date. Status: draft, posted, reversed or cancelled. The header button Enter receipt opens the new receipt form. When empty, the page says "No receipt exists yet. Post a customer invoice first."

Fields

This screen has no form.

Actions

Button/link What it does Permission Confirmation Result
Enter receipt Opens the new receipt form. finance.receipt.draft None /receipts/new
Receipt link Opens the receipt. finance.receipt.view None /receipts/{id}

Rules and messages

None. This page is read only.

Role differences

Enter receipt is hidden without finance.receipt.draft. Without finance.receipt.view the page shows "Not allowed".

Enter customer receipt

Click Enter receipt on the receipts list. Route: /receipts/new. The form posts to /receipts. Page title: Enter customer receipt (Arabic: إدخال سند قبض من عميل).

New receipt form with allocations

New receipt form with numbered callouts

Callouts: (1) Receipt amount (gross paid by the customer); (2) Bank charges withheld; (3) Currency; (4) the Allocations table with the Allocate checkboxes and Allocation amounts.

The page explains: "Enter the cash received and the posted invoices it pays. Each invoice can take up to its open balance. Allocations must not add up to more than the receipt amount; any remainder is kept as a customer advance. Choose invoices of the same customer. The receipt must be posted by a different user."

The Receipt section holds the header fields. The Allocations table lists every posted invoice with an open balance, with Allocate, Invoice, Customer, Invoice total, Open balance and Allocation.

If there is no active branch or customer, the page says "An active branch and an active customer are needed before a receipt can be entered." If no posted invoice has an open balance, the page says "No posted invoice has an open balance. Post a customer invoice first." In that case the form is not shown at all, so a receipt that is entirely an advance cannot be entered until at least one invoice is open (see the gaps file).

Fields

Field Required What to enter / rules
Branch Yes Active branch that receives the money. Its code starts the receipt number.
Customer Yes Active customer profile. Allocated invoices must belong to this customer.
Bank or payment reference Yes Bank or payment reference, at most 128 characters. It must be unique for the customer.
Receipt amount (gross paid by the customer) Yes Positive decimal. Stored exactly as typed, with up to 10 decimal places.
Bank charges withheld No Default 0. "Booked as an expense. The bank is debited for the amount less charges." Must be zero or more and less than the receipt amount.
Currency No Defaults to the base currency. "The base currency is {currency}. A receipt in another currency needs a company exchange rate for today and must be fully allocated to invoices; unapplied foreign advances are not supported."
match[] (column "Allocate", one checkbox per invoice) No Tick each invoice this receipt pays.
amount[id] (column "Allocation") Yes for each ticked invoice Amount allocated to that invoice. Positive. At most the invoice's open balance.

Actions

Button/link What it does Permission Confirmation Result
Save draft (/receipts) Saves a draft receipt with its allocations. finance.receipt.draft None (unsaved-changes warning if you leave) Opens /receipts/{id} in status draft.
Back to receipts Returns to the list without saving. None Browser warning if the form was changed /receipts

Rules and messages

  • "Receipt reference is required and must be at most 128 characters."
  • "Receipt amount must be a decimal greater than zero."
  • "Bank charges must be a decimal of zero or more." / "Bank charges must be less than the receipt amount."
  • "Allocation amounts must be decimals greater than zero."
  • "Each invoice can be allocated once per receipt."
  • "Allocations exceed the receipt amount." (over-allocation of the receipt)
  • "The allocation is more than the invoice open balance." (over-allocation of an invoice)
  • "The invoice belongs to another customer."
  • "Only a posted invoice can receive a receipt."
  • "The invoice is in {currency}. Allocate a receipt in the same currency."
  • "Branch was not found." / "Customer was not found." / "The customer profile must be active before a receipt can be entered."
  • "A foreign-currency receipt must be fully allocated to invoices. Unapplied foreign advances are not supported."
  • "No {currency} exchange rate is entered on or before {date}."
  • "This receipt reference is already entered for this customer."

Role differences

Without finance.receipt.draft the page shows "Not allowed", and the Enter receipt button is hidden on the list.

Receipt detail

Open a receipt from the list, from an invoice's Settlement table, or after saving the draft. Route: /receipts/{id}. Page title: Receipt {number} (Arabic: سند القبض {number}).

Posted receipt with allocation, refund and reversal sections

Posted receipt with numbered callouts

Callouts: (1) Move an allocation; (2) Refund unapplied advance; (3) Apply unapplied balance; (4) Reverse this receipt. Sections 2 and 3 appear only when the receipt has an unapplied balance.

Posted receipt in Arabic (right to left)

Picture not taken (08-receipt-draft). The demo has no draft receipt. The reason is in the screenshot index.

The subtitle explains the status:

  • Draft: "Draft. Posting debits bank and cash and clears the allocated invoices. Its allocations are held until then."
  • Posted: "Posted on {date}. This receipt is not edited. An unapplied balance can still be applied to an open invoice. Reversing it restores the invoices so they can be credited."
  • Reversed: "Reversed." followed by the reason and the reversal journal number.
  • Cancelled: "Cancelled. Its draft allocations were released."

Panels:

  1. Summary: Number, Status, Customer, Branch, Reference, Receipt date, Currency, Amount, Bank charges and Net to bank (when charges exist), Unapplied (when posted) and Journal.
  2. Allocations: Invoice, Kind (Receipt allocation, Applied advance, Moved from another invoice), Status (active or moved) and Amount. With no allocation, the panel says "No invoice is allocated. The whole amount is an unapplied customer advance."
  3. Move an allocation (posted receipt, finance.receipt.post, at least one active allocation and another open invoice of the customer). There is one row per active allocation.
  4. Moved allocations: From, To, Amount, Reason, Journal.
  5. Refunds: Reference, Status ("requested" for a refund waiting to be posted, then posted or cancelled), Amount, Reason, Journal, and the buttons Post refund and Cancel.
  6. Refund unapplied advance (posted receipt with an unapplied balance, finance.receipt.draft).
  7. Posted journal.
  8. Draft action bar: Cancel draft and Post receipt.
  9. Apply unapplied balance (posted receipt with an unapplied balance and an open invoice, finance.receipt.post).
  10. Reverse this receipt (posted receipt, finance.receipt.post).

What the journals do:

  • Posting debits 1000 Bank and cash with the net amount and 6100 Bank charges with the charges. It credits 1100 Accounts receivable for each allocation and 2300 Customer advances for any remainder. For a foreign-currency receipt, the exchange difference goes to 7100 Realised FX.
  • Applying an advance debits 2300 and credits 1100.
  • Moving an allocation moves the receivable between the two invoices. Cash is not touched.
  • Posting a refund debits 2300 and credits 1000.
  • Reversing a receipt posts a reversing journal. The bank is credited and the invoices open again.

Fields

Move an allocation (one form per active allocation, posts to /receipts/{id}/allocations/{id}/move):

Field Required What to enter / rules
From (shown only) The invoice and amount being moved.
invoice_id (label "To invoice") Yes Another open invoice of the same customer.
Amount Yes Defaults to the whole allocation. Positive. At most the allocation, and at most the target's open balance.
Reason Yes 3 to 255 characters.

Refund unapplied advance (posts to /receipts/{id}/refunds):

Field Required What to enter / rules
Amount Yes Defaults to the unapplied balance. Positive. At most the unapplied balance and at most the cash the bank received.
Payment reference Yes Reference of the outgoing refund payment. At most 128 characters, unique on this receipt.
Reason Yes 3 to 255 characters.

Apply unapplied balance (posts to /receipts/{id}/apply):

Field Required What to enter / rules
invoice_id (label "Open invoice of this customer") Yes Open invoice of the same customer, shown with its open amount.
Amount to apply Yes Positive. At most the unapplied balance and the invoice's open balance.

Reverse this receipt (posts to /receipts/{id}/reverse):

Field Required What to enter / rules
Reason Yes 3 to 255 characters.

Actions

Button/link What it does Permission Confirmation Result
Post receipt (/receipts/{id}/post) Numbers and posts the draft. finance.receipt.post (not the user who entered it) "Post this receipt? It is numbered and cannot be edited afterwards." Status posted. The invoices' open balances drop.
Cancel draft (/receipts/{id}/cancel) Cancels the draft and releases its allocations. finance.receipt.draft "Cancel this draft receipt? Its allocations will be released." Status cancelled.
Move Moves all or part of an allocation to another invoice. finance.receipt.post (not the user who posted the receipt) "Move this allocation to the chosen invoice?" A row appears in Moved allocations. The old allocation becomes moved.
Request refund (/receipts/{id}/refunds) Reserves part of the unapplied balance for a cash refund. finance.receipt.draft None A requested row appears in Refunds.
Post refund (/receipts/{id}/refunds/{id}/post) Posts the refund journal. finance.receipt.post (not the user who requested it) "Post this refund? The bank is credited and it cannot be edited." Refund posted, journal shown.
Cancel (refund, /receipts/{id}/refunds/{id}/cancel) Cancels a requested refund. The amount becomes unapplied again. finance.receipt.draft "Cancel this refund request?" Refund cancelled.
Apply to invoice Applies the unapplied balance to an open invoice. finance.receipt.post None A row of kind "Applied advance" appears. Unapplied drops.
Reverse receipt Reverses the whole receipt, including advances applied later. finance.receipt.post (not the user who posted it) "Reverse this whole receipt? The bank is credited and the invoices open again." Status reversed.

The route pattern for the refund buttons is /receipts/{id}/refunds/{id}/(post|cancel).

Staging only: posting, reversing, moving and refunding are irreversible ledger postings. A reversal is permanent. Run them only with test data.

Rules and messages

Posting and cancelling:

  • "Only a draft receipt can be posted. Posted receipts are not edited." (also the answer to a second click)
  • "The person who entered a receipt cannot post it."
  • At posting, the allocation checks from the new receipt form are run again, so an invoice paid in the meantime is refused with "The allocation is more than the invoice open balance."
  • "Only a draft receipt can be cancelled."

Moving an allocation:

  • "Give a reason of 3 to 255 characters."
  • "The person who posted a receipt cannot move its allocations."
  • "Only an active allocation can be moved."
  • "Choose a different invoice to move the allocation to."
  • "The amount is more than the allocation."
  • "Amount must be a decimal greater than zero."
  • "This is not built for foreign-currency receipts. Reverse the receipt and enter it again."

Refunds:

  • "Refund amount must be a decimal greater than zero."
  • "Give a reason of 3 to 255 characters."
  • "Refund payment reference is required and must be at most 128 characters."
  • "The refund is more than the receipt has unapplied."
  • "Refunds cannot exceed the cash the bank received for this receipt."
  • "This refund reference is already used on this receipt."
  • "Only a requested refund can be posted." / "The person who requested a refund cannot post it." / "The receipt is no longer posted. Cancel this refund."
  • "Only a requested refund can be cancelled."

Applying the unapplied balance:

  • "Amount must be a decimal greater than zero."
  • "Only a posted receipt can apply its unapplied balance."
  • "The amount is more than the receipt has unapplied."
  • "This receipt is already allocated to this invoice."
  • "The allocation is more than the invoice open balance."

Reversing:

  • "Give a reason of 3 to 255 characters."
  • "Only a posted receipt can be reversed."
  • "The person who posted a receipt cannot reverse it."
  • "A refund request is pending on this receipt. Post or cancel it first."

Role differences

Missing permission Effect
finance.receipt.post Post receipt, Move, Post refund, Apply to invoice and Reverse receipt are hidden (Company administrator).
finance.receipt.draft Cancel draft, Request refund and refund Cancel are hidden.
finance.receipt.view The page shows "Not allowed".

The Move and Reverse sections are shown to the user who posted the receipt, even though the server will refuse them for that user. A second user with finance.receipt.post must do these steps.

Procedures

Bill a job and post the invoice

  1. As an AR accountant, open the job and go to the Customer invoices panel.
  2. Choose a Tax code, or leave "No tax".
  3. Click Create invoice draft from {n} approved line(s). The draft invoice opens.
  4. Check Lines, the Total and the customer.
  5. Click Post invoice, then Confirm.
  6. The status changes to posted. The number and the Posted journal panel appear.
  7. Click PDF (English) or PDF (العربية) to download the tax invoice.

Correct a posted invoice with a partial credit note and re-bill

  1. Open the posted invoice.
  2. In Partial credit, tick the line in the Credit column.
  3. Enter a Quantity or an amount in or amount.
  4. Enter the Credit reason.
  5. To bill the line again later, credit it in full and tick Reopen the credited sell lines for re-billing.
  6. Click Draft partial credit note. The draft note opens.
  7. Click Post credit note, then Confirm.
  8. Back on the invoice, the Credited column and the Settlement panel show the credit. If you reopened the lines, the job shows them as billable again. Create a new draft from the job; its lines show "Re-billed from".

Add a charge with a debit note

  1. Open the posted invoice.
  2. In Debit note, enter the first Charge description and Amount.
  3. Enter the Reason.
  4. Click Draft debit note.
  5. On the draft, click Post debit note, then Confirm. The invoice's open balance grows by the note total.

Record a customer receipt (two users)

  1. User A (AR accountant) opens Finance > Receipts and clicks Enter receipt.
  2. User A chooses the Branch and Customer, and enters the Bank or payment reference, the Receipt amount (gross paid by the customer) and, if the bank deducted charges, Bank charges withheld.
  3. User A ticks the invoices in Allocate and enters each Allocation. Anything left over becomes a customer advance.
  4. User A clicks Save draft.
  5. User B (another AR accountant or the Finance manager) opens the draft receipt and clicks Post receipt, then Confirm.
  6. Check the invoice's Settlement panel: Allocated by receipts went up and Open balance went down.

Apply or refund a customer advance

  1. Open the posted receipt. Unapplied shows the advance.
  2. To apply it: in Apply unapplied balance, choose the invoice under Open invoice of this customer, enter Amount to apply and click Apply to invoice.
  3. To refund it: in Refund unapplied advance, enter the Amount, Payment reference and Reason, and click Request refund.
  4. A different user with finance.receipt.post clicks Post refund in the Refunds table and confirms.

Move an allocation to another invoice

  1. A user who did not post the receipt opens the posted receipt.
  2. In Move an allocation, choose the target under To invoice, adjust the Amount and enter the Reason.
  3. Click Move, then Confirm. The move appears in Moved allocations with its journal.

Reverse a receipt

  1. Make sure no refund is in "requested" status. Post or cancel it first.
  2. A user who did not post the receipt enters the Reason in Reverse this receipt and clicks Reverse receipt, then Confirm.
  3. The receipt becomes reversed, and the invoices it paid are open again.

Troubleshooting

Symptom Cause What to do
The job page has no draft invoice button. No approved, unbilled sell lines, or no finance.invoice.draft. Approve the sell lines (job costing chapter) or ask for the permission.
"This would take the customer over the credit limit (…). Ask for an approved credit override." The customer's exposure exceeds the credit limit. Ask for a credit override, or collect payment first.
"A sell line on this draft changed. Cancel the draft and create it again." A sell line was changed after drafting. Click Cancel draft and create a new draft from the job.
"The tax rate changed after this invoice was drafted. Cancel the draft and create it again." A new tax rate came into force. Cancel the draft and draft again.
"The fiscal period is not open for posting." Today's period is closed. Ask the Finance manager (chapter 10).
"This invoice has a receipt allocated. Reverse the receipt or credit part of it." A full credit was attempted on a paid invoice. Use Partial credit up to the open balance, or have the receipt reversed first.
"The credit is more than the invoice open balance." The credit is larger than what is still unpaid. Reduce the amount, or move or reverse the receipt allocation first.
"The person who entered a receipt cannot post it." Maker-checker. Ask a second user with finance.receipt.post.
"Allocations exceed the receipt amount." The allocations add up to more than the receipt. Reduce the allocations.
"This receipt reference is already entered for this customer." Duplicate entry of the same payment. Search the receipts list. The receipt probably exists already.
"Only a posted invoice can be printed." The invoice is a draft or cancelled. Post the invoice first.
The PDF still shows the old total after a credit note. The PDF is stored at first generation and reused. This is expected. Print the credit note details from the system, or give the customer both documents.
"The form token is not valid. Reload the page and try again." (page "Form expired") The page was open too long or the session changed. Reload the page and submit again. Check first that the action did not already happen.
"Not allowed" after clicking a button Your role lacks the permission. Ask an administrator.

9. Payables: supplier bills, credit notes and payments

This chapter covers money owed to suppliers and carriers. It explains how to enter a supplier bill and match it to accrued carrier costs, enter costs that were never accrued, post the bill, and see the price difference (variance). It also covers correcting a posted bill with a supplier credit note and paying bills in a payment batch: draft, release, export of the bank file, then bank confirmation or failure.

No bank is connected to this application. Export bank file only writes a CSV file to private storage, and Download bank file lets you download it. Someone must upload the file to the bank outside the application. Nothing is sent to a bank, and the batch is not paid until a user records the bank's confirmation.

Who uses it:

Role (exact name) What the role can do here Permission keys
AP accountant Enter, post and cancel supplier bills. Draft supplier credit notes and post another user's notes. Draft and cancel payment batches. finance.supplier_bill.view, finance.supplier_bill.draft, finance.supplier_bill.post, finance.supplier_payment.view, finance.supplier_payment.draft
Treasury user View bills and batches. Release batches, export and download the bank file, record bank confirmation or failure. Approve beneficiary bank accounts. finance.supplier_bill.view, finance.supplier_payment.view, finance.supplier_payment.release, finance.supplier_payment.export, partners.bank.approve
Finance manager Everything above. All of the keys above
Company administrator View and draft supplier bills only. finance.supplier_bill.view, finance.supplier_bill.draft

Specification: Spec §16 (suppliers, agents and intercompany settlement), §17.1 and §17.2 (accrual matching), §17.3 (foreign currency). UML: 17 (job charges and accruals), 18 (supplier bills and correction notes), 20 (treasury and bank reconciliation), 35 (financial document posting lifecycle), 37 (open-item settlement) and 44 (supplier bill accrual matching and variance).

How posting works in this area

  • Exact decimals and rounding. Bill line amounts, supplier credit amounts and payment amounts are rounded to the currency's minor units (half away from zero). Input tax is rounded the same way. Use a dot as the decimal separator and no thousands separators. The "greater than zero" check is made before rounding, so an amount such as 0.004 is not refused and becomes 0.00. Do not enter amounts below the currency's smallest unit.
  • Posted documents are not edited. A posted bill or credit note has no edit form. Only drafts can be cancelled. A batch can be cancelled only while it is draft or released.
  • Atomic posting. Posting a bill or credit note, or confirming a payment, runs in one transaction: number, journal, open items, carrier-cost status and audit event. A refusal saves nothing. Numbers look like {BRANCH}-SB-2026-000001 (bill), {BRANCH}-SC-… (supplier credit note) and {BRANCH}-SP-… (payment batch, numbered when it is released).
  • Posting date. Bills and credit notes post on the day you post them, not on the bill date. A payment journal is dated on the day the bank confirmation is recorded, not on the batch payment date. A closed fiscal period refuses posting (chapter 10).
  • Maker-checker as built. The person who drafted a supplier credit note cannot post it. The person who drafted a payment batch cannot release it. No second person is required to post a supplier bill, to export a batch, or to confirm or fail it. No amount thresholds exist (docs/OPEN_QUESTIONS.md Q11 is open). Do not assume a limit.
  • Double submission. Submit buttons are disabled while a request is in flight. A second request is refused by the status checks, for example "Only a draft supplier bill can be posted. Posted bills are not edited." or "Only a released payment batch can be exported, once."
  • Refusals are shown on an English error page with the message quoted here, a Return to the start page button and a reference number. Use the browser Back button to return.

Supplier bills list

Open Finance > Supplier bills (Arabic: فواتير الموردين). Route: /supplier-bills.

Supplier bills list

The subtitle explains the flow: "A supplier bill matches accrued carrier costs of one supplier, or records costs with no accrual. Posting clears each accrual, books the difference and any direct cost to freight expense by job (or to cost suspense when no job is known), and credits accounts payable. Posted bills are not edited; corrections are supplier credit notes."

Columns: Bill (number or "Draft #id"), Supplier, Supplier reference, Status (draft, posted, cancelled), Total and Bill date. Header buttons: Supplier payments and Enter supplier bill. When empty: "No supplier bill exists yet."

Fields

This screen has no form.

Actions

Button/link What it does Permission Confirmation Result
Enter supplier bill Opens the new bill form. finance.supplier_bill.draft None /supplier-bills/new
Supplier payments Opens the payment batches list. finance.supplier_payment.view None /supplier-payments
Bill link Opens the bill. finance.supplier_bill.view None /supplier-bills/{id}

Rules and messages

Read only.

Role differences

Enter supplier bill is hidden without finance.supplier_bill.draft (Treasury user). Without finance.supplier_bill.view the page shows "Not allowed" with "Your roles do not include this page. Ask an administrator if you need access."

Enter supplier bill

Click Enter supplier bill. Route: /supplier-bills/new. The form posts to /supplier-bills. Page title: Enter supplier bill (Arabic: إدخال فاتورة مورد).

New supplier bill form

New supplier bill with numbered callouts

Callouts: (1) Bill details; (2) the first Match checkbox of Accrued carrier costs; (3) the first Description row of Costs with no accrual; (4) Save draft.

The subtitle: "Match accrued carrier costs, enter costs that were never accrued, or both. A matched line defaults to the accrued amount; a different amount books the difference to freight expense on posting. A cost line with no accrual is expensed to its job, or held in the cost suspense account when no job is chosen yet."

The page has three parts:

  • Bill details: the header fields.
  • Accrued carrier costs: carrier costs that operations accrued and that are waiting for a bill. Columns: Match, Supplier, Consolidation, Accrued amount and Bill amount. When none are waiting, the table says "No accrued carrier cost is waiting for a supplier bill."
  • Costs with no accrual: three free rows with Description, Job and Amount.

If no supplier is active, the page says "No active supplier exists. Activate a supplier profile first."

Fields

Field Required What to enter / rules
Supplier Yes Active supplier (payee) profile. Matched carrier costs must have this supplier as payee.
Supplier invoice reference Yes The supplier's invoice number, at most 128 characters. It must be unique for the supplier.
Bill date Yes Date on the supplier's invoice (default today). Used for ageing. It is not the posting date.
Branch (cost lines only) No "From the carrier cost or job" by default. Choose a branch when the bill has only cost lines with no job.
Currency No Defaults to the base currency. "A foreign currency needs an exchange rate and can only carry cost lines with no accrual."
Input tax No "No tax", or an input tax code with its rate. Recoverable tax goes to 1400 Input tax. Tax that is not recoverable is added to the cost.
match[] (column "Match", one checkbox per accrued cost) No Tick each accrued carrier cost this bill covers.
amount[id] (column "Bill amount") Yes for ticked rows Defaults to the accrued amount. Change it to what the supplier actually billed. The difference is the variance.
direct_description[] (column "Description", 3 rows) Yes for a used row What the cost is, at most 255 characters.
direct_job[] (column "Job") No The job to charge, or "No job yet (cost suspense)".
direct_amount[] (column "Amount") Yes for a used row Positive amount, rounded to minor units.

Actions

Button/link What it does Permission Confirmation Result
Save draft (/supplier-bills) Saves a draft bill. Matched carrier costs are held for it. finance.supplier_bill.draft None (unsaved-changes warning) Opens /supplier-bills/{id} in status draft.
Back to supplier bills Leaves without saving. None Browser warning if changed /supplier-bills

Rules and messages

  • "Supplier reference is required and must be at most 128 characters."
  • "Bill date must be a valid date."
  • "Select at least one accrued carrier cost or enter a cost line."
  • "Each cost line needs a description of at most 255 characters."
  • "Bill amounts must be decimals." / "Bill amounts must be greater than zero."
  • "Each accrued carrier cost can be matched once."
  • "Supplier was not found." / "The supplier profile must be active before a bill can be entered."
  • "Accrued carrier costs are held in {base}. A foreign-currency bill can only carry cost lines with no accrual."
  • "Currency {code} is not an active currency."
  • "Carrier cost was not found." / "Only an accrued carrier cost can be matched."
  • "The bill supplier must be the payee of the carrier cost."
  • "Supplier bills need the company base currency. No exchange rate is configured."
  • "This carrier cost is already on a supplier bill."
  • "Carrier costs from different branches cannot share one bill."
  • "Job was not found." / "Branch was not found."
  • "Choose a branch for a bill with no carrier cost or job."
  • "This supplier reference is already entered for this supplier."

Role differences

Without finance.supplier_bill.draft the page shows "Not allowed" (Treasury user).

Supplier bill detail

Open a bill from the list, from a payment batch, or after saving. Route: /supplier-bills/{id}. Page title: Supplier bill {number} (Arabic: فاتورة المورد {number}).

Posted supplier bill with variance, payments and credit note form

Supplier bill with numbered callouts

Callouts: (1) the settlement figures: Credited, Paid (bank confirmed), In payment batches, awaiting bank and Outstanding; (2) the Supplier credit note form.

The subtitle explains the status:

  • Draft: "Draft. Its carrier costs are held for this bill. Posting clears the accruals and credits accounts payable."
  • Posted: "Posted on {date}. This bill is not edited."
  • Cancelled: "Cancelled. Its carrier costs were released."

Panels:

  1. Summary: Number, Status, Supplier, Supplier reference, Bill date, Currency, Total and Journal. On a posted bill, a second list shows Credited, Paid (bank confirmed), In payment batches, awaiting bank, and Outstanding.
  2. Bill lines: #, Cost, Accrued, Bill amount, Difference (bill amount minus accrued amount, the variance), Status and Credited. A cost line with no accrual shows "no accrual", and its Cost reads "… — job {reference}" or "… — cost suspense".
  3. Supplier credit notes: Credit note, Supplier reference, Status, Total and Reason.
  4. Payments: Batch, Status (an exported batch shows "exported, awaiting bank confirmation") and Amount.
  5. Posted journal: Account, Job, Debit and Credit.
  6. Draft: Cancel draft and Post bill. Posted, for users with finance.supplier_bill.draft: the Supplier credit note form.

What posting does and how variance is booked.

  • Each matched carrier cost debits 2100 Accrued freight for the amount that was accrued.
  • If the bill amount differs from the accrued amount, the difference (the variance) is booked to freight expense on the same jobs, in proportion to how the accrual was spread. A higher bill adds expense; a lower bill reduces it.
  • A cost line with no accrual is expensed to its job, or to 5900 Cost suspense when no job was chosen.
  • Recoverable input tax debits 1400.
  • 2000 Accounts payable is credited for the bill total.
  • The matched carrier costs become "matched".

Posting checks that nothing has changed since the draft.

Fields

Supplier credit note form (posts to /supplier-bills/{id}/credit-note):

Supplier credit note form on a posted bill

Field Required What to enter / rules
line[] (column "Credit", one checkbox per bill line with something left) At least one Tick each bill line the supplier credits.
credit[id] (column "Amount") Yes for ticked lines Credit amount. At most the column "Left to credit". Rounded to minor units.
Supplier credit note reference Yes The supplier's credit note number, at most 128 characters, unique for the supplier.
Reason Yes At most 255 characters.

Actions

Button/link What it does Permission Confirmation Result
Post bill (/supplier-bills/{id}/post) Numbers and posts the draft. finance.supplier_bill.post "Post this bill? It is numbered and cannot be edited afterwards." Status posted, number and journal shown.
Cancel draft (/supplier-bills/{id}/cancel) Cancels the draft and releases its carrier costs. finance.supplier_bill.draft "Cancel this draft bill? Its carrier costs will be released." Status cancelled.
Draft supplier credit note Creates a draft supplier credit note. finance.supplier_bill.draft None (unsaved-changes warning) Opens /supplier-credit-notes/{id}.

Staging only: posting a bill is irreversible. Correct it only with a supplier credit note.

Rules and messages

Posting:

  • "Only a draft supplier bill can be posted. Posted bills are not edited."
  • "A supplier bill needs at least one line."
  • "The tax rate changed after this bill was drafted. Cancel the bill and enter it again."
  • "The bill total changed after it was drafted."
  • "A foreign-currency bill can only carry cost lines with no accrual."
  • "A carrier cost on this bill changed. Cancel the bill and enter it again."
  • "The accrued amount changed after this bill was drafted."
  • "The accrual has no freight expense to split a variance against."
  • "Branch was not found."
  • Period refusals (chapter 10).

Cancelling: "Only a draft supplier bill can be cancelled."

Supplier credit note drafting:

  • "Supplier credit note reference is required and must be at most 128 characters."
  • "A reason is required and must be at most 255 characters."
  • "Choose at least one bill line to credit." / "Each bill line can be credited once per note."
  • "Credit amounts must be decimals." / "Credit amounts must be greater than zero."
  • "Only a posted supplier bill can be credited."
  • "Supplier credit notes on foreign-currency bills are not built yet."
  • "This bill already has a draft credit note. Post or cancel it first."
  • "A credited line does not belong to this bill."
  • "The credit is more than this bill line has left."
  • "The credit is more than the bill open balance."
  • "This supplier credit note reference is already entered for this supplier."

Role differences

Missing permission Effect
finance.supplier_bill.post Post bill is hidden (Company administrator).
finance.supplier_bill.draft Cancel draft and the credit note form are hidden (Treasury user).
finance.supplier_bill.view The page shows "Not allowed".

Supplier credit note

Open a note from the bill's Supplier credit notes panel or right after drafting it. Route: /supplier-credit-notes/{id}. Page title: Supplier credit note {number} (Arabic: الإشعار الدائن من المورد {number}).

Picture not taken (09-supplier-credit-note). The demo has no supplier credit note. The reason is in the screenshot index.

The subtitle explains the status:

  • Draft: "Draft. Posting debits accounts payable and credits the expense the bill lines went to. The person who drafted it cannot post it."
  • Posted: "Posted. This credit note is not edited."
  • Cancelled: "Cancelled. The bill was not changed."

Panels: Summary (Number, Status, Bill, Supplier, Supplier reference, Currency, Total, Reason, Journal), Lines (#, Bill line, Amount), Posted journal, and for a draft Cancel draft and Post supplier credit note.

Posting debits 2000 Accounts payable. It credits the expense the bill lines went to: the job's freight expense, the variance jobs, or 5900 Cost suspense. It also credits 1400 Input tax for the recoverable tax.

Fields

This screen has no input fields.

Actions

Button/link What it does Permission Confirmation Result
Post supplier credit note (/supplier-credit-notes/{id}/post) Numbers and posts the note. finance.supplier_bill.post (not the user who drafted it) "Post this supplier credit note? It cannot be edited afterwards." Status posted. The bill's Credited and Outstanding are updated.
Cancel draft (/supplier-credit-notes/{id}/cancel) Cancels the draft. finance.supplier_bill.draft "Cancel this draft credit note? The bill will not change." Status cancelled.

Both buttons use the route pattern /supplier-credit-notes/{id}/(post|cancel).

Rules and messages

  • "Only a draft supplier credit note can be posted."
  • "The person who drafted a supplier credit note cannot post it."
  • "The bill is no longer posted. Cancel this credit note."
  • "Supplier credit notes on foreign-currency bills are not built yet."
  • "The bill was paid or credited after this draft. Cancel this credit note."
  • "A credit line is more than its bill line has left."
  • "The credit total no longer matches its lines."
  • "Only a draft supplier credit note can be cancelled."
  • "Supplier credit note was not found."

Role differences

The post button is hidden without finance.supplier_bill.post. Cancel is hidden without finance.supplier_bill.draft. A user who holds both permissions sees Post supplier credit note on their own draft, but the server refuses it with the maker-checker message.

Supplier payments list

Open Finance > Supplier payments (Arabic: مدفوعات الموردين), or click Supplier payments on the supplier bills list. Route: /supplier-payments.

Supplier payment batches list

Supplier payment batches list on a phone

Columns: Batch (number after release, otherwise "Draft #id"), Branch, Payment date, Bills, Total, Status and Bank reference. Status values:

Status Meaning
draft Amounts reserved against the bills. Another person must release it.
released Numbered and ready for export.
exported (shown "exported, awaiting bank confirmation") The bank file was written. Nothing is posted yet.
confirmed The bank confirmed. The payment journal is posted.
failed The bank rejected the batch. Nothing was posted and the bills are open again.
cancelled Cancelled while draft or released. The bills are open again.

Header button: New payment batch. When empty: "No payment batch exists yet."

Fields

This screen has no form.

Actions

Button/link What it does Permission Confirmation Result
New payment batch Opens the batch form. finance.supplier_payment.draft None /supplier-payments/new
Batch link Opens the batch. finance.supplier_payment.view None /supplier-payments/{id}

Rules and messages

Read only.

Role differences

New payment batch is hidden without finance.supplier_payment.draft (Treasury user). Without finance.supplier_payment.view the page shows "Not allowed" (Company administrator).

New payment batch

Click New payment batch. Route: /supplier-payments/new. The form posts to /supplier-payments. Page title: New payment batch (Arabic: دفعة مدفوعات جديدة).

New payment batch form

The subtitle: "Choose one branch and the bills to pay. Each amount is capped at the bill's open balance. A supplier needs an approved bank account in {currency}. Another person releases the batch before it can be exported."

Batch details holds the header. Bills to pay lists each posted bill with an open balance: Pay, Bill, Branch, Supplier, Open, Bank account and Amount. A bill whose supplier has no approved bank account shows "none approved", and its checkbox and amount are disabled. The bill opens for payment once a Treasury user or Finance manager approves the supplier's bank account (beneficiary change) in the partner record. That approval goes through /bank-accounts/{id}/approve, which needs partners.bank.approve. It is described in the partners chapter. Approving redirects to the partners list instead of the partner (known defect D11). When no bill is open: "No posted supplier bill has an open balance."

Fields

Field Required What to enter / rules
Branch Yes The branch paying. Every bill in the batch must be from this branch.
Payment date Yes Planned payment date (default today). It is written to the bank file. The journal is dated when the bank confirms.
bill[] (column "Pay") At least one Tick each bill to pay. Disabled when the supplier has no approved bank account.
amount[id] (column "Amount") Yes for ticked bills Defaults to the open balance. Positive, rounded to minor units, at most the open balance.

Actions

Button/link What it does Permission Confirmation Result
Draft payment batch (/supplier-payments) Saves a draft batch and reserves the amounts. finance.supplier_payment.draft None (unsaved-changes warning) Opens /supplier-payments/{id} in status draft.
Back to supplier payments Leaves without saving. None Browser warning if changed /supplier-payments

Rules and messages

  • "Payment date must be a valid date."
  • "Select at least one supplier bill to pay."
  • "Each supplier bill can appear once in a batch."
  • "Payment amounts must be decimals." / "Payment amounts must be greater than zero."
  • "Branch was not found." / "Supplier bill was not found."
  • "Only a posted supplier bill can be paid."
  • "Bills from different branches cannot share one payment batch."
  • "Bills in different currencies cannot share one payment batch."
  • "The payment is more than the bill's open balance ({open})."
  • "The supplier has no approved bank account in {currency}."

Role differences

Without finance.supplier_payment.draft the page shows "Not allowed" (Treasury user).

Payment batch detail

Open a batch from the list or from a bill's Payments panel. Route: /supplier-payments/{id}. Page title: Payment batch {number} (Arabic: دفعة المدفوعات {number}).

Exported payment batch awaiting bank confirmation

Payment batch with numbered callouts

Callouts: (1) Bank file SHA-256; (2) Download bank file; (3) Record bank confirmation; (4) Bank rejected the batch.

Payment batch in Arabic (right to left)

The subtitle explains the status:

  • Draft: "Draft. The amounts are reserved against the bills. Another person must release it before export."
  • Released: "Released. Export the bank file next. Each bank account is checked against its approval first."
  • Exported: "Exported, awaiting bank confirmation. Nothing is posted and the bills are not shown as paid until the bank confirms."
  • Confirmed: "Confirmed by the bank. The payment is posted."
  • Failed: "The bank rejected this batch. Nothing was posted and the bills are open again."
  • Cancelled: "Cancelled. The bills are open again."

Panels:

  1. Summary: Number, Status, Branch, Payment date, Currency, Total, Bank reference and Journal. After export it also shows Bank file SHA-256, and after a failure the Failure reason.
  2. Bills: #, Bill, Supplier, Bank account (bank, holder and the last four characters of the IBAN), Approval ("current", or "changed since drafting" when the bank account approval changed after the batch was drafted) and Amount.
  3. Posted journal, after confirmation: Dr 2000 Accounts payable, Cr 1000 Bank and cash.
  4. Action bar: Cancel batch, Download bank file, Export bank file and Release batch, depending on status and permission.
  5. When exported, for users with finance.supplier_payment.release: Record bank confirmation and Bank rejected the batch.

The bank file. Export bank file writes a CSV file to the application's private storage. The file name is the batch number and the columns are batch, line, payment_date, beneficiary, iban, swift, bank, amount, currency and remittance. Its SHA-256 is recorded. Download bank file (/supplier-payments/{id}/file) checks the stored file against that SHA-256 before sending it. The file contains the full IBAN and SWIFT of every beneficiary, so store and send it securely. Downloads are recorded in the audit log (supplier_payment.downloaded).

Fields

Record bank confirmation (posts to /supplier-payments/{id}/confirm):

Field Required What to enter / rules
Bank confirmation reference Yes The bank's reference for the executed payment, at most 128 characters.

Bank rejected the batch (posts to /supplier-payments/{id}/fail):

Field Required What to enter / rules
Reason Yes Why the bank rejected it, at most 255 characters.

Actions

Button/link What it does Permission Confirmation Result
Release batch (/supplier-payments/{id}/release) Numbers the batch and re-checks every beneficiary approval. finance.supplier_payment.release (not the user who drafted it) "Release this payment batch for export?" Status released, number {BRANCH}-SP-….
Export bank file (/supplier-payments/{id}/export) Writes the bank CSV once, after re-checking approvals. finance.supplier_payment.export "Export the bank file for this batch?" Status exported. Bank file SHA-256 shown.
Download bank file Downloads the stored CSV. finance.supplier_payment.export None CSV file {batch number}.csv.
Confirm and post payment Records the bank reference and posts the payment journal. finance.supplier_payment.release "Confirm the bank payment and post it? This cannot be undone." Status confirmed. The bills show the amount under Paid (bank confirmed).
Mark failed Records the rejection. Nothing is posted. finance.supplier_payment.release "Mark this batch as failed? The bills open again." Status failed.
Cancel batch (/supplier-payments/{id}/cancel) Cancels a draft or released batch. finance.supplier_payment.draft "Cancel this payment batch? The bills open again." Status cancelled.

The route pattern for all batch buttons is /supplier-payments/{id}/(release|export|confirm|fail|cancel).

Staging only: confirming a payment posts to the ledger and cannot be undone. Export once per batch. Do not upload test bank files to a real bank.

Rules and messages

  • "Only a draft payment batch can be released."
  • "The person who drafted a payment batch cannot release it."
  • "Beneficiary approval is not valid for this account." (release or export after the bank account approval changed)
  • "Only a released payment batch can be exported, once."
  • "A bank file for this batch already exists."
  • "This batch has not been exported." (download before export)
  • "The bank file is missing or does not match the file that was exported." (the stored file was changed or removed)
  • "The bank confirmation reference is required and must be at most 128 characters."
  • "Only an exported batch awaiting the bank can be confirmed."
  • "The batch total does not match its lines."
  • "A failure reason is required and must be at most 255 characters."
  • "Only an exported batch awaiting the bank can be marked failed."
  • "Only a draft or released batch can be cancelled. An exported batch is marked failed if the bank rejects it."
  • "Payment batch was not found."

Role differences

Role What it sees
AP accountant Cancel batch only (draft and released). No release, export, download or confirmation.
Treasury user Release batch, Export bank file, Download bank file, and the confirmation and failure forms. No Cancel batch.
Finance manager All buttons. Cannot release a batch they drafted.
Company administrator "Not allowed".

Procedures

Enter and post a bill that matches an accrual, with a variance

  1. As an AP accountant, open Finance > Supplier bills and click Enter supplier bill.
  2. Choose the Supplier, enter the Supplier invoice reference and check the Bill date. Choose Input tax if the invoice has tax.
  3. In Accrued carrier costs, tick Match for the carrier cost and change Bill amount to the billed amount (for example 1,050.00 against 1,000.00 accrued).
  4. Click Save draft.
  5. Check Bill lines: Difference shows 50.00.
  6. Click Post bill, then Confirm.
  7. In Posted journal: Dr 2100 1,000.00, Dr freight expense 50.00 on the job, Cr 2000 1,050.00 (plus tax lines if any).

Enter a cost that was never accrued

  1. In Enter supplier bill, leave the accrued table unticked.
  2. In Costs with no accrual, enter the Description, choose the Job (or "No job yet (cost suspense)") and enter the Amount.
  3. If no job is chosen, choose Branch (cost lines only).
  4. Click Save draft, then Post bill.

Record a supplier credit note (two users)

  1. User A opens the posted bill. In Supplier credit note, tick the line and enter the Amount, the Supplier credit note reference and the Reason.
  2. User A clicks Draft supplier credit note.
  3. User B (another AP accountant or the Finance manager) opens the draft and clicks Post supplier credit note, then Confirm.
  4. The bill's Credited and Outstanding figures change.

Pay bills through a batch

  1. Make sure each supplier has an approved bank account in the bill currency (partners chapter).
  2. AP accountant: open Finance > Supplier payments, click New payment batch, choose the Branch and Payment date, tick Pay for each bill, check the Amount, and click Draft payment batch.
  3. Treasury user (not the drafter): open the batch, click Release batch, Confirm.
  4. Treasury user: click Export bank file, Confirm. Then click Download bank file.
  5. Outside the application, upload the file to the bank. No bank is connected to this system.
  6. When the bank confirms, the Treasury user enters the Bank confirmation reference and clicks Confirm and post payment, Confirm. If the bank rejects the batch, enter the Reason under Bank rejected the batch and click Mark failed, Confirm.
  7. Check the bill: Paid (bank confirmed) shows the amount. While the batch was exported, the amount showed under In payment batches, awaiting bank.

Troubleshooting

Symptom Cause What to do
The carrier cost is not in Accrued carrier costs. It is not accrued yet, it is already on another bill, or it is held by a draft bill. Accrue it from the job, or cancel the other draft.
"The bill supplier must be the payee of the carrier cost." The wrong supplier was chosen. Choose the payee shown in the Supplier column.
"Accrued carrier costs are held in {base}. A foreign-currency bill can only carry cost lines with no accrual." A foreign currency was entered with matched accruals. Enter matched bills in the base currency.
"The accrued amount changed after this bill was drafted." The accrual was changed after drafting. Cancel the draft and enter the bill again.
"This supplier reference is already entered for this supplier." Duplicate bill. Search the list for the existing bill.
"The person who drafted a supplier credit note cannot post it." Maker-checker. Ask another user with finance.supplier_bill.post.
The bill row is greyed out with "none approved". No approved bank account for the supplier in that currency. Ask Treasury to approve the beneficiary bank account.
"The person who drafted a payment batch cannot release it." Maker-checker. Ask another user with finance.supplier_payment.release.
"Beneficiary approval is not valid for this account." / Approval shows "changed since drafting" The bank account was changed or re-approved after drafting. Cancel the batch and draft it again with the current account.
"The bank file is missing or does not match the file that was exported." The stored file was altered or removed. Developer-assisted: investigate the private storage. Do not pay from an altered file.
"Only a draft or released batch can be cancelled. An exported batch is marked failed if the bank rejects it." You tried to cancel an exported batch. Use Mark failed if the bank rejected it. Otherwise wait for confirmation.
The bill still shows as unpaid after export. Export is not payment. Record the bank confirmation.

10. General ledger, bank, FX, tax, recognition and period close

This chapter covers the company general ledger and month-end work:

  • the posted journals overview and the chart of accounts;
  • manual journals with maker-checker approval, scheduled auto-reversals and reversal;
  • bank statement import and reconciliation;
  • exchange rates, rate import and foreign-currency revaluation;
  • tax codes and effective-dated rates;
  • revenue recognition;
  • intercompany charges;
  • fiscal period close (soft close, hard close, reopening).

No tax submission provider is live. Tax codes and the tax report work inside the application. Electronic submission to a tax authority (TaxSubmissionProvider, shown on /integrations) is built but not connected: it refuses with "The tax profile has no api_key secret." until a live profile is configured (docs/LIVE_PROVIDER_CHECKLIST.md, LIVE04). No bank is connected either: statements are imported from CSV files.

Who uses it:

Role (exact name) What the role can do here Permission keys
Finance manager Everything in this chapter: approve and post manual journals, manage accounts, rates, tax codes, recognition, intercompany, period close and reopening. finance.journal.view, finance.journal.draft, finance.journal.approve, finance.journal.control, finance.period.adjust, finance.account.manage, finance.bank.view, finance.bank.reconcile, finance.fx.rate.manage, finance.fx.revalue, finance.tax.manage, finance.settings.configure, finance.recognition.run, finance.period.close, finance.period.reopen, finance.intercompany.view, finance.intercompany.post, finance.intercompany.accept, dashboard.finance.view
AR accountant, AP accountant View journals, accounts, rates, tax codes and recognition. Draft and cancel manual journals. The AP accountant can also view intercompany. finance.journal.view, finance.journal.draft (AP: also finance.intercompany.view)
Treasury user Bank reconciliation. Enters exchange rates only through a direct request, because the /fx page needs finance.journal.view, which this role does not have (see Role differences). finance.bank.view, finance.bank.reconcile, finance.fx.rate.manage, partners.bank.approve
Auditor Read-only journals, accounts, manual journals, rates, tax codes and recognition. finance.journal.view
Company administrator Read-only journals, accounts, manual journals, rates, tax codes and recognition. finance.journal.view

Specification: Spec §14 (chart of accounts and posting), §15-§17 (documents and accruals), §17.3 (foreign currency), §17.4 (recognition), §18 (tax), §19 (period close and reopening), §16 (intercompany). UML: 19 (general ledger and posting), 20 (treasury and bank reconciliation), 34 (job financial close), 35 (financial document posting), 36 (fiscal period close and reopen) and 43-45 (posting sequences).

How posting works in this area

  • Exact decimals. Manual journal amounts are refused with "Amounts are positive decimals with at most {n} decimal places." when they have more decimals than the base currency (2 for most currencies). Exchange rates allow up to 10 decimal places. Tax rates are percentages from 0 to 100 with at most 4 decimal places. Intercompany amounts allow at most two decimals. Use a dot as the decimal separator.
  • Balanced journals only. Every journal must have debits equal to credits: "Debits and credits must be equal." and, at posting, "The journal does not balance."
  • Posted journals are never edited. You correct them with a reversing journal. Document journals (invoices, bills, receipts, payments) are corrected through their documents: "This journal came from a document. Reverse or credit the document instead."
  • Atomic posting. Each posting runs in one transaction with its audit event. A refusal saves nothing. Journal numbers look like JE-2026-000001.
  • Dates. "A journal cannot be posted to a future date." A posting into a soft-closed period is refused with "The fiscal period is soft-closed. Only designated adjustments can post to it." A posting into a hard-closed period is refused with "The fiscal period is not open for posting."
  • Maker-checker as built.
  • Manual journals: "The person who drafted a manual journal cannot approve it."
  • Period reopening: "The person who asked to reopen a period cannot approve it."
  • Intercompany: "The user who raised the charge cannot accept it for the counterpart company."
  • Rejecting or cancelling a manual journal, closing a period, revaluing and recognising do not need a second person.
  • No amount thresholds exist (docs/OPEN_QUESTIONS.md Q11 is open).
  • Scheduled work. The bounded cron job posts due manual-journal reversals, due FX revaluation reversals and pending recognition. The buttons on the screens do the same thing on demand.
  • Refusals are shown on an English error page with the message, a Return to the start page button and a reference. Use the browser Back button to return.

Finance overview (journals)

Open Finance > Finance overview (Arabic: نظرة عامة على المالية) in the sidebar. Route: /finance. Page title: Finance (Arabic: المالية).

Finance overview with areas, KPIs and posted journals

Finance overview in Arabic

The subtitle: "Posted journals only. An accrual is freight expense and accrued freight payable. A supplier bill clears that accrual against accounts payable. A customer invoice posts accounts receivable and freight revenue. A hard-closed period rejects later posting. Posted lines are not edited on this page."

The page has three parts:

  • Finance areas (Arabic: أقسام المالية): buttons to Customer invoices, Customer receipts, Credit notes, Supplier bills, Supplier payments, Bank reconciliation, Manual journals, Chart of accounts, Revenue recognition, Exchange rates, Tax codes, Period close and Reports. This is how you reach the screens that are not in the sidebar.
  • KPIs, for users with dashboard.finance.view: Accrued carrier cost, Accrued and not yet billed, Supplier payable, Customer receivable, Customer advances, Recognized revenue, Recognized cost and Recognized gross profit.
  • One panel per posted journal: number, type, status, "Accounting date {date}. Source {type} {id}.", and a table of Account, Job (internal id), Debit and Credit. The list is newest first. It has no search or paging. When empty: "No journal is posted for this company."

Fields

This screen has no form.

Actions

Button/link What it does Permission Confirmation Result
Period close and checklist Opens period close. finance.period.close None /close
Finance areas buttons Open each finance screen. The target's permission None The target page

Rules and messages

Read only. Journals are never edited here.

Role differences

The KPIs are hidden without dashboard.finance.view. Period close and checklist is hidden without finance.period.close. Without finance.journal.view (Treasury user) the page shows "Not allowed" with "Your roles do not include this page. Ask an administrator if you need access."

Chart of accounts

Click Chart of accounts in Finance areas, or the header button on manual journals. Route: /ledger/accounts. Page title: Chart of accounts (Arabic: دليل الحسابات).

Chart of accounts

Add an account form

The subtitle: "System accounts are used by document posting and stay active. Control accounts are locked against ordinary manual journals; their balances come from invoices, bills, receipts and payments. An account with a balance cannot be deactivated, and posting outside an account's active dates is refused."

The Accounts table shows Code, Name (with a "system" tag), Type, Control (the control role), Manual posting ("allowed" or "locked"), Status, Active dates ("{from} to {to}") and Balance (Dr − Cr). With finance.account.manage it also has an Activation column with a form for each non-system account. Filter this page… filters the visible rows in the browser.

Fields

Add an account (posts to /ledger/accounts):

Field Required What to enter / rules
Code Yes "4 to 8 digits." Unique. Codes reserved for system accounts are refused.
Name Yes 3 to 120 characters.
Type Yes (default expense) asset, liability, equity, revenue or expense.

Activation row form (posts to /ledger/accounts/{id}/activation):

Field Required What to enter / rules
status (Status) Yes active or inactive.
active_from (Active from) No First date the account may receive postings.
active_to (Active to) No Last date. Must not be before Active from.

Actions

Button/link What it does Permission Confirmation Result
Add account Creates the account. finance.account.manage None The account appears in the list.
Save (Activation column) Changes status and active dates. finance.account.manage None The row is updated.
Journals / Manual journals Navigation. finance.journal.view None /finance, /manual-journals

Rules and messages

  • "An account code is 4 to 8 digits."
  • "An account name is 3 to 120 characters."
  • "Choose asset, liability, equity, revenue or expense."
  • "An account with this code already exists."
  • "This code is reserved for a system account."
  • "Choose active or inactive."
  • "Enter dates as YYYY-MM-DD."
  • "The active-to date is before the active-from date."
  • "A system account used by document posting stays active."
  • "An account with a balance cannot be deactivated. Clear the balance first."
  • "Lines are already posted to this account after the active-to date."
  • At posting: "Account {code} is not active for posting on {date}."

Role differences

Without finance.account.manage, the Activation column and Add an account are hidden (everyone except the Finance manager). System accounts never show an activation form.

Manual journals

Click Manual journals in Finance areas. Route: /manual-journals. Page title: Manual journals (Arabic: القيود اليدوية).

Manual journals and scheduled reversals

The subtitle: "A manual journal is drafted by one person and approved and posted by another. Control accounts need a correction journal with the control permission, and receivable or payable lines must name the invoice or bill they adjust. A posted journal is never edited; reverse it instead."

Manual journals lists #, Kind ("standard" or "correction", plus ", adjustment" when flagged), Date, Memo, Total, Status (draft, posted, rejected, cancelled, reversed), Journal (number once posted), Auto-reverse and Drafted by. When empty: "No manual journal is drafted for this company."

Scheduled reversals lists the auto-reversals waiting to post: Journal, Source, Reverse on, Status, Reversal (number once posted) and Last refusal. Last refusal shows the error code of the last failed attempt, for example period_closed. When empty: "No reversal is scheduled."

Fields

This screen has no input fields.

Actions

Button/link What it does Permission Confirmation Result
New manual journal Opens the draft form. finance.journal.draft None /manual-journals/new
Post reversals due today (/ledger/reversals/run) Posts every pending scheduled reversal whose date has come (also done by cron). finance.journal.approve "Post every reversal due today?" Reversals posted. Refused ones keep their Last refusal code.
Journal # link Opens the journal. finance.journal.view None /manual-journals/{id}

Staging only: Post reversals due today posts journals for every due item in the company at once.

Rules and messages

Scheduled reversals are posted as designated adjustments, so they can post into a soft-closed period but not into a hard-closed one. A refused reversal stays pending and is retried.

Role differences

New manual journal is hidden without finance.journal.draft (Auditor, Company administrator). Post reversals due today is hidden without finance.journal.approve (everyone except the Finance manager).

New manual journal

Click New manual journal. Route: /manual-journals/new. The form posts to /manual-journals. Page title: New manual journal (Arabic: قيد يدوي جديد).

New manual journal form

New manual journal with numbered callouts

Callouts: (1) Journal details; (2) Period-end adjustment (may post to a soft-closed period); (3) the first line's Account; (4) the first line's open item type; (5) Save draft.

The subtitle: "Amounts are in {currency}. Each line has a debit or a credit. Debits must equal credits. Another person approves and posts it." Lines has six rows. Blank rows are ignored. A control account shows "(control)" after its name.

Fields

Field Required What to enter / rules
Kind Yes "standard", or "correction or opening (control accounts)". The second choice appears only with finance.journal.control.
Accounting date Yes Date of the journal. It must not be in the future at posting and must be in an open period (or a soft-closed one for an adjustment).
Auto-reverse on No A date after the accounting date. On that date the journal is reversed automatically.
Reference No At most 64 characters.
Memo Yes 3 to 255 characters.
Period-end adjustment (may post to a soft-closed period) No Tick for an adjustment into a soft-closed period. Approval then needs finance.period.adjust.
account[] (Account, line {n}) Yes for a used line Account. A control account needs Kind = correction.
debit[] (Debit, line {n}) One of debit/credit Positive, at most the currency's decimals.
credit[] (Credit, line {n}) One of debit/credit Positive, at most the currency's decimals.
job[] (Job id, line {n}) No Internal job id (a number), to put the line on a job.
item_type[] (Open item type, line {n}) For receivable/payable control lines invoice or supplier bill.
item_id[] (Open item id, line {n}) With the type The internal id of the posted invoice or bill.
description[] (Description, line {n}) No At most 255 characters.

Actions

Button/link What it does Permission Confirmation Result
Save draft (/manual-journals) Validates and saves a draft. Nothing is posted. finance.journal.draft (correction: also finance.journal.control) None (unsaved-changes warning) Opens /manual-journals/{id} in status draft.
Back to manual journals Leaves without saving. None Browser warning if changed /manual-journals

Rules and messages

  • "Choose a standard or a correction journal."
  • "Enter the accounting date as YYYY-MM-DD."
  • "The reversal date must be a valid date after the accounting date."
  • "Give a memo of 3 to 255 characters."
  • "A reference is at most 64 characters."
  • "A journal needs 2 to 200 lines."
  • "Amounts are positive decimals with at most {n} decimal places."
  • "Each line has either a debit or a credit greater than zero."
  • "Account was not found." / "Job was not found."
  • "Account {code} is a control account. Its balance comes from documents; use a correction journal with approval."
  • "A correction to account {code} must name the posted invoice it adjusts." (or "…supplier bill it adjusts.")
  • "The named open item is not a posted document of this company."
  • "A manual correction can only adjust a document in the base currency."
  • "Debits and credits must be equal."
  • "You do not have permission for this action." (correction kind without finance.journal.control)

Role differences

Without finance.journal.draft the page shows "Not allowed" (Auditor, Company administrator, Treasury user). Only the Finance manager sees the correction kind.

Manual journal detail

Open a journal from the list. Route: /manual-journals/{id}. Page title: Manual journal #{id} (Arabic: القيد اليدوي رقم {id}).

Draft manual journal with approval actions

Posted manual journal with the reversal form

The subtitle explains the status:

  • Draft: "Draft. Another person with approval permission must approve and post it."
  • Posted: "Posted as {number}. It is not edited; reverse it to undo it."
  • Reversed: "Posted as {number} and reversed."
  • Rejected: "Rejected: {reason}"
  • Cancelled: "Cancelled. Nothing was posted."

Summary shows Number, Status, Kind, Accounting date, Total, Reference, Drafted by, Approved by, Auto-reverse on (with the schedule status), Reversal ("{number} on {date}") and Memo. Lines shows each line with its Job and Open item. Actions holds the buttons below.

Fields

Field Required What to enter / rules
Rejection reason Yes to reject 3 to 255 characters.
Reversal date Yes to reverse Default today. It must not be before the journal date or in the future.
Reversal reason Yes to reverse 3 to 255 characters. Used as the memo of the reversing journal.

Actions

Button/link What it does Permission Confirmation Result
Approve and post (/manual-journals/{id}/post) Re-validates and posts the journal. Schedules its auto-reversal if set. finance.journal.approve; correction also finance.journal.control; adjustment also finance.period.adjust. Not the drafter. "Approve and post this journal? A posted journal cannot be edited." Status posted, journal number shown.
Reject (/manual-journals/{id}/reject) Rejects the draft with a reason. finance.journal.approve "Reject this journal?" Status rejected.
Cancel draft (/manual-journals/{id}/cancel) Cancels the draft. finance.journal.draft "Cancel this draft? Nothing will be posted." Status cancelled.
Reverse (/manual-journals/{id}/reverse) Posts a reversing journal on the reversal date. Cancels any pending auto-reversal. finance.journal.approve "Reverse this posted journal? A reversing journal will be posted." Status reversed. Reversal shown in Summary.

The route pattern for all four is /manual-journals/{id}/(post|reject|cancel|reverse).

Rules and messages

  • "Only a draft manual journal can be approved."
  • "The person who drafted a manual journal cannot approve it."
  • "Only a draft manual journal can be rejected." / "Only a draft manual journal can be cancelled."
  • "Give a reason of 3 to 255 characters."
  • "Enter the reversal date as YYYY-MM-DD."
  • "Only a posted manual journal can be reversed."
  • "The reversal date is before the journal date."
  • "This journal is already reversed."
  • Period and date refusals (see "How posting works in this area").

Role differences

Role What it sees on a draft On a posted journal
Finance manager Approve and post, Reject, Cancel draft Reverse
AR or AP accountant Cancel draft only Nothing
Auditor, Company administrator Nothing (read only) Nothing

The drafter can also reject or cancel their own draft. Only approval needs a second person.

Bank reconciliation

Open Finance > Bank reconciliation (Arabic: التسوية البنكية). Route: /bank. One statement opens with /bank?statement={id}.

Picture not taken (10-bank). The demo has no bank statement. The reason is in the screenshot index.

Picture not taken (10-bank-annotated). The demo has no bank statement. The reason is in the screenshot index.

Callouts: (1) Import statement; (2) the Statements table; (3) a Match form on a statement line.

The subtitle: "Import the bank's CSV statement for the company bank account and match each statement line to the posted bank journal line of the same amount. A matched journal line is reconciled. Matching never changes a posted journal."

  • Statements lists Statement (file name), Imported, Lines, Matched, Money in and Money out.
  • When a statement is opened, its lines show #, Date, Description, Reference, Amount, Status and Journal line. An unmatched line offers a list of unreconciled bank journal lines with the same amount (journal number, source and date) and a Match button. If no candidate exists, it says "No unreconciled bank journal line has this amount." A matched line shows its journal and an Unmatch link.

The statement is for the company bank account (account 1000). No bank feed is connected; the statement is a file you download from your bank.

Beneficiary bank accounts. Supplier and partner bank accounts are approved from the partner record with Approve (/bank-accounts/{id}/approve, permission partners.bank.approve). This is described in the partners chapter. After approval the page goes to the partners list instead of back to the partner (known defect D11).

Fields

Field Required What to enter / rules
Statement file Yes "CSV with columns date, description, reference, amount; money in is positive." UTF-8, at most 2 MB and 5,000 lines. Dates as YYYY-MM-DD. Amounts are decimals with a dot and are never zero.
Journal line for statement line {n} (journal_line_id) Yes to match Choose the posted bank journal line.

Actions

Button/link What it does Permission Confirmation Result
Import statement (/bank/statements) Reads and stores the statement. The same file cannot be imported twice. finance.bank.reconcile None (upload progress shown) Opens /bank?statement={id}.
Match (/bank/lines/{id}/match) Reconciles the statement line with the chosen journal line. finance.bank.reconcile None The line shows status matched.
Unmatch (/bank/lines/{id}/unmatch) Removes the match. finance.bank.reconcile "Unmatch this statement line? The journal line becomes unreconciled." The line is unmatched again.
Approve bank account (/bank-accounts/{id}/approve, partner page) Approves a proposed beneficiary account. partners.bank.approve (not the proposer) See the partners chapter Redirects to /partners (D11).

Both statement-line buttons use the route pattern /bank/lines/{id}/(match|unmatch).

Rules and messages

  • "Choose a CSV statement file to import."
  • "The statement file must be a CSV of at most 2 MB."
  • "The statement file must be UTF-8 text."
  • "The statement needs the columns date, description, reference and amount."
  • "The statement has no lines." / "A statement can have at most 5000 lines."
  • "Row {n}: the date must be YYYY-MM-DD." / "Row {n}: the amount must be a decimal." / "Row {n}: the amount cannot be zero."
  • "This statement file was already imported."
  • "Statement line was not found." / "Journal line was not found." / "Bank statement was not found."
  • "This statement line is already matched."
  • "Only a posted line on the bank account can be reconciled."
  • "The statement amount and the journal line amount differ."
  • "This journal line is already reconciled to another statement line."
  • "This statement line is not matched."

Unmatched statement lines up to the period end block a hard close (checklist item "Bank statement lines up to the period end not matched to the ledger").

Role differences

The Treasury user and Finance manager can import and match. Users without finance.bank.reconcile do not see Import statement, Match or Unmatch. Without finance.bank.view the page shows "Not allowed" (AR and AP accountants, Auditor, Company administrator).

Exchange rates and revaluation

Click Exchange rates in Finance areas. Route: /fx. Page title: Exchange rates and revaluation (Arabic: أسعار الصرف وإعادة التقييم).

Exchange rates and revaluation

The subtitle: "A rate is the number of {base} for one unit of the foreign currency. A document uses the latest rate dated on or before its date. Rates are never edited; enter a new rate for a new date. Revaluation adjusts open foreign invoices and bills to the rate at the period end and is reversed automatically on the next day."

The page has these panels:

  • Add rate and Import rates, for users with finance.fx.rate.manage.
  • Rates: Currency, Date, Rate, Source and Entered by. When empty: "No exchange rate is entered."
  • Revaluations: As of, Status, Items, Net adjustment, Journal, Reverse on, Reversal and Last refusal. With finance.fx.revalue it also has the Revalue as of form and Post revaluation reversals due today. When empty: "No revaluation has been run."

Fields

Field Required What to enter / rules
Currency Yes An active foreign currency.
Date Yes Rate date. It must not be in the future.
Rate Yes Base currency units for 1 foreign unit. Positive, at most 10 decimal places.
Import CSV (currency, date, rate) Yes to import CSV with currency, date and rate per row. 1 to 1,000 rows, at most 256 KB.
Revalue as of Yes to revalue Revaluation date. It must not be in the future. Usually a period end.

Actions

Button/link What it does Permission Confirmation Result
Add rate (/fx/rates) Stores one rate. finance.fx.rate.manage "Add this rate? Rates cannot be edited afterwards." The rate is listed.
Import rates (/fx/rates/import) Stores every row of the file. finance.fx.rate.manage None (upload progress) The rates are listed.
Revalue open items (/fx/revalue) Posts a revaluation journal for open foreign invoices and bills, and schedules its reversal for the next day. finance.fx.revalue "Revalue open foreign items as of this date? A revaluation journal is posted." A revaluation row with a journal and Reverse on.
Post revaluation reversals due today (/fx/reversals/run) Posts due revaluation reversals (cron does the same). finance.fx.revalue "Post every revaluation reversal due today?" Reversal numbers shown.

Staging only: revaluation and its reversal post journals for every open foreign document of the company.

Rules and messages

  • "Currency {code} is not an active currency."
  • "The base currency has no exchange rate."
  • "Enter rate dates as YYYY-MM-DD."
  • "A rate cannot be dated in the future."
  • "A rate is a positive decimal with at most 10 decimal places."
  • "A {currency} rate is already entered for {date}. Rates are not changed; enter a rate on a later date."
  • "Choose a CSV rate file to import." / "The rate file must be at most 256 KB." / "The rate file needs 1 to 1000 rows." / "Row {n} must have currency, date and rate."
  • "Enter the revaluation date as YYYY-MM-DD." / "A revaluation cannot be dated in the future."
  • "A revaluation is already recorded for {date}."
  • "A document was settled after {date}. Revalue on or after the settlement date."
  • In documents: "No {currency} exchange rate is entered on or before {date}."

Role differences

Role What it sees
Finance manager Everything.
AR or AP accountant, Auditor, Company administrator Rates and revaluations, read only.
Treasury user "Not allowed": the page needs finance.journal.view, although the role has finance.fx.rate.manage (see gaps).

Tax codes

Click Tax codes in Finance areas. Route: /tax. Page title: Tax codes (Arabic: رموز الضريبة).

Tax codes with rate history

New tax code form

The subtitle: "An invoice or bill uses one tax code. Each line keeps the rate in force when it was drafted; if the rate changes before posting, the draft is refused and must be drafted again. Rates are never edited: add a rate from a later date. The report box is where a localisation pack maps the code to a tax return."

No tax submission provider is live. The report box only groups figures in the in-application tax report (/reports/tax, chapter 11). Nothing is filed with a tax authority.

The Tax codes table shows Code, Name, Direction ("Sales (output)" or "Purchases (input)"), Recoverable (input codes only: "Yes" or "No, added to cost"), Report box, Rate today ("None in force" if none), Rate history ("{rate}% from {date}") and Status. With finance.tax.manage, each row has an Add rate form and an Activate or Deactivate button. When empty: "No tax code is set up. Documents are posted without tax." Header buttons: Journals and Tax report.

Fields

New tax code (posts to /tax/codes):

Field Required What to enter / rules
Code Yes 1 to 32 letters, digits, hyphens or underscores. Unique.
Name Yes At most 128 characters.
Direction Yes Sales (output) or Purchases (input).
Input tax recoverable Yes Yes, or "No, add to cost" (only meaningful for input codes).
Report box No At most 32 characters.
Rate Yes Percentage 0 to 100, at most 4 decimal places.
Effective from Yes Default today.

Row Add rate (posts to /tax/codes/{id}/rates):

Field Required What to enter / rules
rate_percent (New rate % for {code}) Yes Percentage 0 to 100, at most 4 decimal places (placeholder "e.g. 14").
effective_from (Effective from) Yes A date with no rate yet.

Actions

Button/link What it does Permission Confirmation Result
Create tax code Creates the code with its first rate. finance.tax.manage None (unsaved-changes warning) The code is listed.
Add rate Adds an effective-dated rate. finance.tax.manage "Add this rate? Rates cannot be edited afterwards." Rate history updated.
Deactivate / Activate (/tax/codes/{id}/status) Changes the code status. finance.tax.manage Deactivate: "Deactivate this tax code?" Status updated. Inactive codes are refused on new documents.
Tax report Opens the tax report. finance.report.view None /reports/tax

Rules and messages

  • "A tax code is 1 to 32 letters, digits, hyphens or underscores."
  • "A tax code name is required and must be at most 128 characters."
  • "Choose output (sales) or input (purchases)."
  • "The report box is at most 32 characters."
  • "A tax rate is a percentage from 0 to 100 with at most 4 decimal places."
  • "Enter the effective date as YYYY-MM-DD."
  • "Tax code {code} already exists."
  • "A rate is already set from {date}. Rates are not changed; add a rate from a later date."
  • "Choose active or inactive." / "Tax code was not found."
  • On documents: "Tax code {code} is inactive.", "Tax code {code} is for sales." (or "…for purchases."), "Tax code {code} has no rate in force on {date}."

Role differences

Without finance.tax.manage, the row forms and New tax code are hidden (everyone except the Finance manager). Without finance.journal.view the page shows "Not allowed" (Treasury user).

Revenue recognition

Click Revenue recognition in Finance areas. Route: /recognition. Page title: Revenue recognition (Arabic: الاعتراف بالإيرادات).

Revenue recognition

The subtitle depends on the basis:

  • Job completion: "Revenue and job costs are recognised when a job is operationally completed. Billing before completion is held as deferred revenue, costs are held in job costs in progress, and completed work not yet billed is held as unbilled receivable."
  • Invoice posting: "Revenue is recognised when the invoice is posted and job costs are expensed when they are accrued or billed."

Recognition basis (with finance.settings.configure) is a select (Job completion or Invoice posting) with Save basis. Once revenue is posted on the basis, the select is disabled with "Revenue has been posted on this basis, so it cannot be changed."

Jobs lists Job, Operations, Status (plus "pending"), Revenue recognised, Cost recognised, Deferred, Unbilled, Costs in progress, Runs and a Recognise button for pending jobs. When empty: "No completed job is waiting for recognition."

Fields

Field Required What to enter / rules
Recognition basis Yes Job completion or Invoice posting. Locked after the first recognition on it.

Actions

Button/link What it does Permission Confirmation Result
Save basis (/recognition/basis) Sets the basis. finance.settings.configure "Change the recognition basis? It is locked once revenue is posted on it." Subtitle changes.
Recognise all pending jobs (/recognition/run) Recognises up to 50 pending jobs (job completion basis only). finance.recognition.run "Recognise revenue and cost for every pending job? Recognition journals are posted." Jobs show recognised amounts and runs.
Recognise (/recognition/jobs/{id}) Recognises one job. finance.recognition.run "Recognise revenue and cost for this job? A recognition journal is posted." The row is updated.

Staging only: Recognise all pending jobs posts recognition journals for many jobs at once.

Rules and messages

  • "Choose job completion or invoice."
  • "Revenue has been posted on the current basis. Changing it needs a transition plan, which is not built."
  • "Revenue is recognised on invoice for this company." (recognising on the invoice basis)
  • "Revenue is recognised when the job is operationally completed." (recognising a job that is not completed)
  • "Job was not found."

Completed jobs waiting for recognition block a hard close (checklist item "Completed jobs waiting for revenue and cost recognition").

Role differences

Only the Finance manager sees Recognition basis and the recognise buttons. Viewers with finance.journal.view see the page read only.

Intercompany

Open Finance > Intercompany (Arabic: المعاملات بين الشركات). Route: /intercompany. The page is opened with GET. The coverage list shows it as an action, but it is the page itself.

Intercompany relations, charges and reconciliation

The subtitle: "Charges between companies of this installation. Each company posts its own side, in its own transaction and periods. Nothing is posted in another company from here."

The page has these panels:

  • Relations: Company, Base currency, Our side and Their side ("not enabled"). With finance.intercompany.post there is a form to enable or disable our side: "The other company enables its own side. Charging needs both."
  • Raise a charge: "Posts Dr 1500 due from / Cr 4900 intercompany revenue here. The counterpart posts Dr 5800 / Cr 2600 when it accepts."
  • Incoming charges and Outgoing charges: Number, From/To, Date, Service, Amount and Status. The status is one of Awaiting counterpart, Counterpart failed (with the error and attempt number), Posted both sides, Rejected or Reversed.
  • Reconciliation and elimination, per counterparty: Ledger due from, Ledger due to, Matched due from, Matched due to, One-sided due from, One-sided charges, Net eliminable and Ledger ties (Yes/No). "No elimination journal is posted; group consolidation is a later reporting extension."

To accept an incoming charge, switch to the receiving company (company switcher in the top bar, chapter 2) as a user who has finance.intercompany.accept there.

Fields

Field Required What to enter / rules
counterparty_id (Company) Yes The other company.
active (Our side) Yes Enable or Disable.
target_company_id (Company, under Raise a charge) Yes Only companies where both sides are enabled.
Amount Yes Positive, at most two decimals, in the shared base currency.
Charge date No Default today.
Service Yes What is charged, at most 255 characters.
accounting_date (Accounting date (blank = charge date)) No Date for our side when accepting.
Rejection reason (reason) Yes to reject Why the charge is refused.

Actions

Button/link What it does Permission Confirmation Result
Open Intercompany (/intercompany, GET) Shows the page. finance.intercompany.view None The page.
Save our side (/intercompany/relations) Enables or disables charging with that company. finance.intercompany.post None Our side updated.
Raise and post our side (/intercompany/charges) Creates the charge and posts Dr 1500 / Cr 4900 in this company. finance.intercompany.post "Raise this charge and post our side? The posting cannot be edited." Outgoing charge Awaiting counterpart.
Accept and post / Retry (/intercompany/charges/{id}/accept) Posts Dr 5800 / Cr 2600 in the receiving company. finance.intercompany.accept (not the user who raised it) "Accept this charge and post our side?" Posted both sides, or Counterpart failed with the error (for example a closed period). Retry is offered.
Reject (/intercompany/charges/{id}/reject) Refuses the incoming charge. finance.intercompany.accept "Reject this charge?" Rejected with the reason.
Reverse our side (/intercompany/charges/{id}/reverse) Posts a reversing journal for a rejected outgoing charge. finance.intercompany.post "Reverse our side of this charge? A reversing journal is posted." Reversed.

The route pattern for the charge buttons is /intercompany/charges/{id}/(accept|reject|reverse).

Staging only: intercompany postings affect two companies' ledgers. Use the demo companies only.

Rules and messages

  • "A company cannot charge itself."
  • "Both companies must enable intercompany charging with each other first."
  • "Intercompany charges between companies with different base currencies are not supported yet."
  • "Enter a positive amount with at most two decimals."
  • "Enter a valid charge date."
  • "Describe the service being charged (up to 255 characters)."
  • "This charge is not awaiting acceptance."
  • "The user who raised the charge cannot accept it for the counterpart company."
  • "Give a reason for rejecting the charge."
  • "Only a rejected charge is reversed."
  • "The charge was not found." / "The company was not found."

Role differences

Role What it sees
Finance manager All panels and all actions.
AP accountant View only (no relation form, no Raise a charge, no Accept/Reject/Reverse).
Others "Not allowed".

Period close

The close period screen is /periods.

Click Period close in Finance areas or Period close and checklist on the Finance overview. Route: /close. Page title: Period close (Arabic: إقفال الفترات).

Period close list with reopening requests

Period close list on a phone

The subtitle: "A soft-closed period accepts only designated adjustments. A hard-closed period accepts no posting. Hard close needs a clear checklist, and periods with postings close in date order. Reopening needs a reason and the approval of a second finance user. Posted documents are never edited, whatever the period status."

  • Periods lists Period (year-number), Dates, Status (open, soft_closed, hard_closed, plus "reopening requested"), Journals, Reopened ("{n} time(s)") and a Checklist button.
  • Reopening requests lists Period, From, Reason, Requested by, Status (with the decision note) and, for pending requests, Approve and Reject with a note. When empty: "No reopening has been requested."

Fields

Field Required What to enter / rules
Approval note (note) No At most 500 characters.
Rejection note (note, placeholder "Reason for rejecting") No At most 500 characters.

Actions

Button/link What it does Permission Confirmation Result
Checklist Opens the period. finance.period.close or finance.period.reopen None /close/periods/{id}
Approve (/close/requests/{id}/approve) Reopens the period. finance.period.reopen (not the requester) "Approve reopening this period? Posting will be allowed again." Period status open, Reopened count + 1.
Reject (/close/requests/{id}/reject) Refuses the request. finance.period.reopen "Reject this reopening request?" Request rejected. The period is unchanged.

The route pattern for both is /close/requests/{id}/(approve|reject).

Staging only: approving a reopening allows posting again in a closed period for every user of the company.

Rules and messages

  • "The decision note must be at most 500 characters."
  • "Reopening request not found."
  • "This reopening request has already been decided."
  • "The person who asked to reopen a period cannot approve it."
  • "The period status changed after the request was made."
  • "Reopen the later closed periods first."

Role differences

Only the Finance manager has finance.period.close and finance.period.reopen. Everyone else gets "Not allowed". Because only one seeded role can reopen, approval needs a second user with the Finance manager role.

Close a period (checklist)

Click Checklist on a period. Route: /close/periods/{id}. Page title: Period {from} to {to} (Arabic: الفترة من {from} إلى {to}).

Period checklist and close actions

Period checklist with numbered callouts

Callouts: (1) the blocking and review counters; (2) Close the period; (3) Ask to reopen.

The subtitle: "The checklist is worked out from the ledger now; the copy taken at each close is kept in the period history." Header buttons: All periods and Trial balance for the period (opens /reports/trial-balance with the period dates).

Checklist shows "{n} blocking item(s)" and "{n} item(s) to review", then each check with its Kind ("Must be clear" or "Review"), Result ("Clear", "Blocking" or "Check before closing"), Count and Figures.

Blocking items (must be clear for a hard close):

  • Manual journals dated in the period and not yet approved.
  • Bank statement lines up to the period end not matched to the ledger.
  • Scheduled reversals due by the period end and not posted.
  • Completed jobs waiting for revenue and cost recognition.
  • Open foreign-currency documents not revalued at the period end.
  • The trial balance for the period balances.
  • Receivable and payable open items reconcile to their control accounts.

Review items:

  • Unposted invoices, notes, receipts, bills and payment batches.
  • Customer advances not yet applied.
  • Output and input tax posted in the period.
  • Unbilled receivable, deferred revenue and job costs in progress.
  • Accrued carrier costs not matched to a supplier bill.
  • Supplier costs waiting for a job (suspense).
  • Jobs with a negative recognised margin.

The check labels are shown in English in both languages.

History lists each close event: When, Event, Status (from → to), By and Reason. When empty: "No close event is recorded for this period."

Fields

Field Required What to enter / rules
I have checked the review items above (reviewed) Yes for hard close when review items are flagged Tick to confirm the review items were checked.
Reason (Ask to reopen) Yes "At least 10 characters.", at most 500.

Actions

Button/link What it does Permission Confirmation Result
Soft-close: allow only adjustments (/close/periods/{id}/soft) Sets the period to soft-closed. Only journals flagged as period-end adjustments post afterwards. finance.period.close "Soft-close this period? Only designated adjustments can be posted afterwards." Status soft_closed, history event.
Hard-close: block all posting (/close/periods/{id}/hard) Sets the period to hard-closed. The button is disabled while blocking items exist. finance.period.close "Hard-close this period? No posting will be accepted in it afterwards." Status hard_closed, checklist copy stored.
Request reopening (/close/periods/{id}/reopen) Creates a reopening request for a second user to decide. finance.period.reopen "Request reopening of this period?" Request pending on /close.
Trial balance for the period Opens the report. finance.report.view None /reports/trial-balance?from=…&to=…

The route pattern for the three period buttons is /close/periods/{id}/(soft|hard|reopen).

Staging only: hard close and reopening are disruptive. A hard-closed period refuses every posting for all users, including document posting dated today if today is in that period. Reopening needs a second Finance manager. Never hard-close a current period on a shared environment.

Rules and messages

  • "Only an open period can be soft-closed."
  • "A period that has not started cannot be closed."
  • "Close the earlier periods that have postings first."
  • "The period is already hard-closed."
  • "Hard-close the earlier periods that have postings first."
  • "The close checklist has blocking items: {keys}." (keys such as bank_reconciliation, recognition)
  • "Confirm that the flagged review items were checked before closing."
  • "Give a reason of 10 to 500 characters for reopening the period."
  • "Only a closed period can be reopened."
  • "Reopen the later closed periods first."
  • "A reopening request for this period is already waiting for approval."
  • "Fiscal period not found."

Role differences

Only the Finance manager sees Close the period and Ask to reopen. Other users get "Not allowed" for the whole page.

Procedures

Draft, approve and auto-reverse an accrual journal

  1. As an AP or AR accountant, open Finance overview > Manual journals and click New manual journal.
  2. Keep Kind standard, enter the Accounting date (the month end), set Auto-reverse on to the first day of the next month, and enter a Memo.
  3. Enter two lines, for example Dr an expense account 500.00 and Cr an accrual liability account 500.00. Click Save draft.
  4. As the Finance manager (a different user), open the journal, click Approve and post, then Confirm.
  5. On the auto-reverse date, cron posts the reversal. To post it at once, the Finance manager clicks Post reversals due today on Manual journals. The journal status becomes reversed.

Correct a receivable with a control journal

  1. As the Finance manager, choose Kind "correction or opening (control accounts)".
  2. On the line for 1100 Accounts receivable, choose Open item type invoice and enter the invoice's internal id as Open item id.
  3. Save the draft. A second Finance manager approves it.

Reconcile the bank

  1. Download the statement CSV from the bank (columns date, description, reference, amount).
  2. Open Finance > Bank reconciliation, choose the Statement file and click Import statement.
  3. For each line, choose the journal line in the list and click Match.
  4. Lines saying "No unreconciled bank journal line has this amount." need a posting first (a receipt, a payment confirmation or a manual journal for bank charges).

Month-end close

  1. Enter and import exchange rates for the month end on Exchange rates.
  2. Run Revalue open items as of the month end (after the month has ended).
  3. Run Recognise all pending jobs on Revenue recognition.
  4. Approve or cancel open manual journals and match the bank.
  5. On Period close, open the period's Checklist. Clear each blocking item and review the others.
  6. Optional: Soft-close: allow only adjustments while adjustments are made.
  7. Tick I have checked the review items above and click Hard-close: block all posting, then Confirm (staging only on shared environments).

Reopen a period (two users)

  1. Finance manager A opens the period, enters a Reason of at least 10 characters and clicks Request reopening.
  2. Finance manager B opens Period close, enters an Approval note and clicks Approve, then Confirm.

Troubleshooting

Symptom Cause What to do
"The person who drafted a manual journal cannot approve it." Maker-checker. Ask another Finance manager.
"Account {code} is a control account…" A control account was used on a standard journal. Use a correction journal (Finance manager), or correct through the document.
"Amounts are positive decimals with at most 2 decimal places." Too many decimals, or a comma. Enter, for example, 1250.50.
"The fiscal period is soft-closed. Only designated adjustments can post to it." The period is soft-closed. Tick Period-end adjustment (approver needs finance.period.adjust), or ask for reopening.
"The fiscal period is not open for posting." The period is hard-closed or missing. Post in an open period, or request reopening.
Scheduled reversal shows Last refusal period_closed The reversal date is in a hard-closed period. Reopen the period or reverse manually with a later date.
"This statement file was already imported." The same file was imported before. Open the existing statement in Statements.
"The statement amount and the journal line amount differ." Amounts differ (for example bank charges). Post the difference first, then match.
"A document was settled after {date}…" A receipt or payment came after the revaluation date. Revalue on or after the settlement date.
"The close checklist has blocking items: …" Blocking items remain. Clear each item listed, then retry.
Hard-close button disabled Blocking items exist. Clear the blocking items.
Treasury user gets "Not allowed" on Exchange rates The page needs finance.journal.view. Developer-assisted: grant journal view, or have the Finance manager enter rates.

11. Financial and management reports

This chapter covers the seven financial reports: trial balance, profit and loss, balance sheet, receivables ageing, payables ageing, job profitability and the tax report. For each report it explains how to choose the dates, what every figure means as implemented, how to download it as CSV and how to print it.

All reports read posted journals or posted documents only, in the company base currency. Drafts are never included. Amounts are shown rounded to the base currency's minor units.

Who uses it:

Role (exact name) Access Permission keys
Finance manager, AR accountant, AP accountant, Auditor, Executive All seven reports, CSV and print finance.report.view
Treasury user, Company administrator, operations and sales roles No report access. The index shows a notice instead. (none)

A user whose finance.report.view grant is limited to a branch or to their own records can open Job profitability, which shows only the jobs that grant covers. The ledger reports (trial balance, profit and loss, balance sheet and both ageings) need a company-wide grant. None of the seeded roles is limited this way.

Specification: Spec §21 (reporting and definitions), §14 (ledger), §15-§18 (documents, accruals, tax). UML: 19 (general ledger), 36 (period close) and 37 (open-item settlement).

Financial reports index

Open Finance > Reports (Arabic: التقارير) in the sidebar, or Reports in Finance areas on the Finance overview. Route: /reports. Page title: Financial reports (Arabic: التقارير المالية).

Financial reports index

Financial reports index in Arabic

The subtitle: "Reports are read from posted journals in the base currency by accounting date. Each can be downloaded as CSV. Ledger reports need company-wide report access; job profitability shows the jobs your access covers."

There is one panel per report. Each panel has a small form and a Show button:

  • Date-range reports with From (default: the first day of the current month) and To (default: today): Trial balance, Profit and loss, Job profitability and Tax report.
  • Point-in-time reports with As of (default: today): Balance sheet, Receivables ageing and Payables ageing.

Without report access the page shows the warning "You do not have report access." (Arabic: ليست لديك صلاحية الوصول إلى التقارير.) and no panels.

Index for a user without report access

Fields

Field Required What to enter / rules
From Yes (range reports) First accounting date included. YYYY-MM-DD.
To Yes (range reports) Last accounting date included. It must not be before From.
As of Yes (point-in-time reports) The date the balances are rebuilt for.

Actions

Button/link What it does Permission Confirmation Result
Show (each panel) Opens the report with the chosen dates (a GET request; nothing is changed). finance.report.view None /reports/{report}?from=…&to=… or ?as_of=…

Rules and messages

The index itself does not refuse access. It shows "You do not have report access." Opening a report URL directly without the permission shows "Not allowed".

Role differences

Users with finance.report.view see all seven panels. Everyone else sees only the notice.

Report view

Click Show on a panel. Route: /reports/{report}, where {report} is one of trial-balance, profit-and-loss, balance-sheet, ar-ageing, ap-ageing, job-profitability or tax. The page title is the report name, for example Trial balance (Arabic: ميزان المراجعة).

Trial balance

Trial balance with numbered callouts

Callouts: (1) the header buttons All reports, Download CSV and Print; (2) the date filter and Show; (3) the balance notice.

The subtitle states the scope: "{from} to {to}, amounts in {currency}." or "As of {date}, amounts in {currency}." The filter at the top repeats the date fields with Show.

Report date filter

On a phone the sidebar collapses, and the wide tables scroll sideways inside their panel.

Trial balance on a phone

Header buttons:

  • All reports goes back to /reports.
  • Download CSV (Arabic: تنزيل CSV) downloads the same report with the same dates: /reports/{report}?…&format=csv. The file is named {report}.csv, for example trial-balance.csv.
  • Print (Arabic: طباعة) opens the browser print dialog. The printout hides the sidebar, top bar, header buttons and date filter, so only the title, scope line and figures are printed.

The CSV is UTF-8 with quoted cells. Any text cell that starts with a spreadsheet formula character is neutralised. Column headings are always in English. In the ageing CSVs the Bucket column holds internal keys (current, days_31_60, days_61_90, over_90) rather than the screen labels.

Fields

Field Required What to enter / rules
From / To Yes (range reports) As on the index.
As of Yes (point-in-time reports) As on the index.

Actions

Button/link What it does Permission Confirmation Result
Show Reloads the report for the new dates. finance.report.view None Updated report
Download CSV Downloads the report as CSV (?format=csv). finance.report.view None {report}.csv
Print Prints the page. None Browser print dialog Paper or PDF via the browser
All reports Returns to the index. None None /reports

Rules and messages

  • "Enter dates as YYYY-MM-DD." (ledger reports) / "Enter report dates as YYYY-MM-DD." (tax report)
  • "The start date must not be after the end date." (ledger reports) / "The start date must be on or before the end date." (tax report)
  • "Ledger reports need company-wide report access." (branch- or owner-limited grant). Shown as the "Not allowed" page.
  • "You do not have permission for this action." (no finance.report.view). Shown as the "Not allowed" page.

These refusals appear on the error page (English) for invalid dates. Fix the dates and click Show again.

Role differences

All roles with finance.report.view see the same figures for the whole company. The exception is Job profitability with a limited grant (see the role table above).

Report definitions

The definitions below describe the calculation as implemented in ReportService and TaxService. Spec §21 requires every report to declare its grain, date basis, scope, currency, inclusion rules and formula.

Trial balance (/reports/trial-balance)

Item Definition
Grain One row per account that has an opening balance or movement. Accounts with neither are hidden.
Date basis Journal accounting date. Posted journals only.
Scope Whole company (all branches). Base currency.
Opening Sum of (debit − credit) of journals dated before From.
Debit / Credit Sums of debits and credits of journals dated From to To inclusive.
Closing debit / Closing credit Opening + Debit − Credit, shown as a closing debit if positive or zero, otherwise as a closing credit.
Total Column sums.
Balance notice "Debits equal credits." when total debits equal total credits, total closing debits equal closing credits and the opening total is zero. Otherwise "Debits do not equal credits."

Profit and loss (/reports/profit-and-loss)

Profit and loss

Item Definition
Grain One row per revenue or expense account with movement in the range.
Date basis Journal accounting date, From to To.
Revenue For each revenue account: credits − debits in the range. Total revenue is their sum.
Expenses For each expense account: debits − credits in the range. Total expenses is their sum.
Net profit Total revenue − Total expenses.
Not included Asset, liability and equity accounts. There is no bridge to managerial estimates on this page.

Balance sheet (/reports/balance-sheet)

Balance sheet

Item Definition
Grain One row per asset, liability or equity account with a balance.
Date basis All posted journals dated on or before As of.
Assets Debits − credits.
Liabilities and Equity Credits − debits.
Earnings of earlier years Net result of revenue and expense accounts before the start of the fiscal year that contains As of. No closing entries are posted.
Earnings of the current year (from {date}) Net result of revenue and expense accounts from the fiscal year start to As of.
Total equity Equity accounts + both earnings lines.
Balance notice "Assets equal liabilities plus equity." or "Assets do not equal liabilities plus equity."

If no fiscal year covers the date, the year start is taken as 1 January of that year.

Receivables ageing (/reports/ar-ageing) and payables ageing (/reports/ap-ageing)

Receivables ageing with callouts for the document columns and the Ageing summary

Item Definition
Control account 1100 for receivables, 2000 for payables.
Grain One row per invoice (receivables) or supplier bill (payables) with a non-zero balance.
Balance Rebuilt from ledger lines on the control account tagged with the document, in posted journals dated on or before As of. Receivables: debit − credit. Payables: credit − debit. Receipts, credits and payments after the date are therefore excluded.
Date / Days Document date (invoice date, or posting date if missing; bill date for bills). Days = As of − document date.
Bucket "0-30 days" (up to 30, including documents dated after As of), "31-60 days", "61-90 days", "Over 90 days".
Ageing summary The bucket totals, then Open items (sum of the document balances) and Control lines with no open item (control-account lines not tagged with any document, for example customer advances or untagged corrections).
Control account Ledger balance of the control account at the date. It equals Open items + Control lines with no open item.
Row order By partner, then document date.

The panel note says: "Each document's balance is rebuilt from the ledger lines tagged with it up to the date, so later receipts and credits are excluded. Age is counted from the document date." Ageing is counted from the document date, not a due date (no payment terms are applied).

Payables ageing

Job profitability (/reports/job-profitability)

Job profitability

Item Definition
Grain One row per job with any non-zero figure. Only jobs the user's report grant covers (branch or owner scope).
Revenue Account 4100 Freight revenue, credits − debits, journals dated From to To, on lines tagged with the job.
Cost Account 5100 Freight expense, debits − credits, in the range.
Margin Revenue − Cost.
Margin % Margin ÷ Revenue × 100, two decimals. Blank when revenue is zero. The footer shows the overall margin %.
Deferred Account 2400 balance (credits − debits) at To.
Unbilled Account 1150 balance (debits − credits) at To.
Costs in progress Account 1300 balance (debits − credits) at To.

The page note: "Revenue and cost are recognised amounts in the range. Deferred, unbilled and costs in progress are balances at the end date." Revenue or cost posted to other accounts, or not tagged with a job, is not included.

Tax report (/reports/tax)

Tax report with the tax summary

Item Definition
Grain One row per tax code used in the range.
Sources and dates Posted (or credited) customer invoices by invoice date; posted credit notes (minus) and debit notes (plus) by note date; posted supplier bills by posting date; posted supplier credit notes (minus) by posting date. Invoices and bills use their base-currency amounts. Customer and supplier credit notes use their amounts as entered, without conversion, so foreign-currency notes are not converted to the base currency. Only documents with a tax code are counted.
Net / Tax Document total minus tax, and the tax, summed with the signs above.
Documents Number of documents counted.
Direction Output, or input; "(not recoverable)" is shown for non-recoverable input codes.
Tax summary Output tax (sum of output codes), Recoverable input tax (sum of recoverable input codes) and Net payable = Output tax − Recoverable input tax. Beside each total, "Ledger 2500: {amount}" shows the net credit movement on 2500 Output tax and "Ledger 1400: {amount}" the net debit movement on 1400 Input tax, by accounting date in the range, so you can compare the report with the ledger. On screen the report amounts are shown in currency format. In the CSV, Net, Tax and the three summary rows are not rounded to the currency's minor units (up to 10 decimals), and the ledger comparison is not exported.

The panel note: "The report reads posted documents by posting date. It is a working summary, not a filed return; a localisation pack maps report boxes to the jurisdiction's return."

No tax submission provider is live. This report is not sent to any tax authority (chapter 10).

Procedures

Run and export the month-end trial balance

  1. Open Finance > Reports.
  2. In Trial balance, set From to the first day of the month and To to the last day. Click Show.
  3. Check that the notice says "Debits equal credits."
  4. Click Download CSV to save trial-balance.csv, or click Print.

You can also open it from the period checklist with Trial balance for the period (chapter 10).

Check receivables against the ledger

  1. In Receivables ageing, set As of to the period end and click Show.
  2. In Ageing summary, compare Open items + Control lines with no open item with Control account 1100. They must be equal.
  3. Investigate a large Control lines with no open item amount: it usually means advances that were not applied (chapter 8).

Prepare tax figures

  1. In Tax report, set the tax period and click Show.
  2. Compare Output tax with Ledger 2500 and Recoverable input tax with Ledger 1400.
  3. Differences usually come from manual journals on tax accounts, or from documents whose posting date differs from their invoice date.

Troubleshooting

Symptom Cause What to do
"You do not have report access." on the index No finance.report.view. Ask an administrator for a role with report access.
"Not allowed" on a ledger report No permission, or a branch-limited report grant. Ask for a company-wide report grant.
"The start date must not be after the end date." From is after To. Correct the dates.
A draft invoice is missing from the reports Reports include posted documents only. Post the document.
The ageing balance differs from the invoice's current open balance The ageing is rebuilt as of the chosen date. Use today's date to see current balances.
"Debits do not equal credits." The ledger does not balance for the range. This should not happen. Developer-assisted: investigate, and record a defect with the reference.
The CSV bucket column shows days_31_60 Internal keys in the CSV. Map: current = 0-30 days, days_31_60 = 31-60 days, days_61_90 = 61-90 days, over_90 = Over 90 days.
The CSV opens in English column headings for an Arabic user Headings are not translated. Expected as built.

12. Master data: partners, imports, reference data and organization

Master data is the shared set of records that every other module uses: business partners (customers, suppliers, carriers, agents and others), their contacts, beneficiary bank accounts and agreements, bulk CSV imports, reference catalogues (currencies, Incoterms, charge codes) and the organization of the active company (branches, business calendars and holidays). References: Spec §3, §4 (4.1 Partner records, 4.2 Reference data, 4.3 Charge codes), §16, §19, §24; UML 01, 04, 05, 06, 24, 56.

Who uses it (seeded roles):

Area Roles Permission keys
Partner list and new partner Company administrator, Sales representative, Sales manager, Operations coordinator, Operations manager, Branch manager, Customs coordinator partners.partner.list, partners.partner.create (Company administrator, Sales representative, Sales manager)
Partner detail Every role with partners.partner.view (also Pricing officer, Pricing manager, Documentation officer, AR accountant, AP accountant, Finance manager, Auditor) partners.partner.view
Contacts Company administrator, Sales representative, Sales manager partners.partner.update
Beneficiary accounts AP accountant proposes; Finance manager approves (Treasury user also holds the approve key, see Role differences) partners.bank.propose, partners.bank.approve
Services and agreements Company administrator manages services and proposes agreements; Finance manager decides and terminates partners.profile.update, partners.agreement.manage, partners.agreement.approve
Partner statement Company administrator, AR accountant, AP accountant, Finance manager partners.statement.view
Imports Company administrator, Finance manager (stage and commit); AP accountant (stage only) data.import.stage, data.import.commit
Reference data Company administrator, Pricing officer, Pricing manager, all operations roles, sales roles reference.data.view, reference.charge.configure
Organization Company administrator organization.branch.create, organization.settings.update (see D12: the page itself has no check)

The demo account browser-staff@nile.test (Platform administrator, Company administrator and Finance manager) can open every screen in this chapter. Partner data is company-specific: a partner's legal identity can be shared, but profiles, balances, bank accounts and agreements belong to the active company.

Partners

Open it from the sidebar: Master data > Partners (Arabic: الشركاء). Route: /partners (GET). The sidebar link is shown only to users holding partners.partner.list.

Partner list for the active company

The partner list in Arabic (right-to-left layout)

The partner list on a phone screen

The page header has a New partner button. The table lists the active company's partner profiles with the columns Code (format P-<year>-<6 digits>, for example P-2026-000012), Legal name, Email and Status. Status is the partner record status (active for new partners). Click a code to open the partner detail. The Filter this page… box filters only the rows already shown in the browser; it does not search the server. When there are no partners the page shows "No partners have been recorded for this company." with a New partner button.

Fields

The list has no form. The filter box is a client-side field (partner-filter) and is not submitted.

Actions

Button/link What it does Permission Confirmation Result
New partner Opens /partners/new none on the route (D12) No Partner form
Partner code link Opens /partners/{id} partners.partner.view No Partner detail

Rules and messages

  • Without partners.partner.list a direct visit to /partners shows the Not allowed page: "Your roles do not include this page. Ask an administrator if you need access."

Role differences

  • AP accountant, Finance manager, Auditor, Pricing officer and AR accountant hold partners.partner.view but not partners.partner.list. They do not see the sidebar link and get Not allowed on /partners, but they can open a partner directly at /partners/{id} (for example from a supplier bill or a search result). Record this when you plan their tests.

Partner form (New partner)

Open it with New partner on the Partners page (Arabic: شريك جديد). Route: /partners/new; the form posts to /partners.

New partner form

The form has two sections: Partner details and Roles. Cancel returns to the list.

Fields

The partner detail fields are generated by one template loop (the fact pack lists them as $label / $name). Each row below is one generated field.

Field Required What to enter / rules
Legal name ($label loop, legal_name) Yes Registered legal name. Empty gives "Legal name is required."
Trading name (trading_name) No Trading or brand name.
Arabic name (name_ar) No Arabic legal or trading name; the input direction follows the text.
Tax ID (tax_id) No Tax registration number. Must be unique across all partners that are not merged.
Country (country_code) No Country code, for example EG. Not validated on this form.
Email (email) No Partner e-mail. Not validated on this form.
Phone (phone) No Partner phone.
Roles (roles) No Pre-filled with customer. Separate several roles with commas, for example customer, shipper. Allowed codes: prospect, customer, supplier, carrier, shipping_line, airline, agent, broker, trucker, warehouse, insurer, shipper, consignee, notify.

Actions

Button/link What it does Permission Confirmation Result
Save Posts to /partners (POST) and creates the partner, its roles and a company profile partners.partner.create No (the browser warns if you leave with unsaved changes) Redirect to the new partner's detail page; audit event partner.created
Cancel Returns to /partners none No No change

Rules and messages

  • "Legal name is required." (422)
  • "A partner with this tax ID already exists: P-…" (422). The check covers every company in the installation, so the message can name a partner code that belongs to another company's profile.
  • Unknown role codes are silently dropped. If no valid role remains, the partner gets the role prospect.
  • Without partners.partner.create the Save answers with the refusal page "You do not have permission for this action." (403).

Role differences

  • The form opens for every internal user (D12: /partners/new has no permission check). Only the save is checked.

Partner detail

Open it by clicking a partner code on the Partners list. Route: /partners/{id}. The page title is the partner's legal name; the status badge is next to it. A Statement button appears for users holding partners.statement.view.

Partner detail with contacts, beneficiary accounts and agreements

Callouts: (1) page header with code, status, roles, services and the Statement button; (2) the add contact form; (3) the Propose a beneficiary account form; (4) Services and agreements.

The summary header shows Code, Status, Roles (tags), Services ("none recorded" when empty) and, after an agency or profit share agreement is approved, Agent agreement (its reference). The page has three panels:

  1. Contacts (جهات الاتصال): the contact list with name and e-mail, and an add form in the panel footer.
  2. Beneficiary accounts (حسابات المستفيدين): "Account numbers are stored encrypted. Only the last four characters are shown." Columns Bank, Account (**** plus the last four characters), Currency, Status, actions. Status values: pending (proposed, waiting for a second person), verified (approved; can receive payments), superseded (replaced by a later verified account for the same partner).
  3. Services and agreements (الخدمات والاتفاقيات): service checkboxes, the agreement table (Reference, Type, Services, Valid, Profit share, Status) and the Propose agreement form. Agreement status values: pending, approved, rejected (with the reason under the badge), terminated. A netting tag is shown for netting agreements.

Addresses: the database has partner address tables, but no screen shows or edits partner addresses in this build (see the gaps file).

Fields

Contacts form (posts to /partners/{id}/contacts):

Field Required What to enter / rules
Name Yes Contact person's name. Empty gives "Contact name is required."
Email No Contact e-mail. Not validated by the server.

Propose a beneficiary account (اقتراح حساب مستفيد), posts to /partners/{id}/bank-accounts, shown only with partners.bank.propose:

Propose a beneficiary account form

Field Required What to enter / rules
Bank Yes Bank name.
Account holder Yes Name on the account.
IBAN or account number Yes 8 to 34 letters and digits. Spaces are removed and letters are upper-cased.
SWIFT / BIC No 8 or 11 letters and digits (field accepts at most 11).
Currency Yes Three-letter currency code; pre-filled EGP.

Services form (posts to /partners/{id}/services), shown only with partners.profile.update: one checkbox per service submitted as service_types[]: Ocean freight, Air freight, Road transport, Customs brokerage, Warehousing, Origin handling, Destination handling, Documentation, Insurance (codes ocean_freight, air_freight, road_transport, customs_brokerage, warehousing, origin_handling, destination_handling, documentation, insurance).

Propose agreement form (posts to /partners/{id}/agreements), shown only with partners.agreement.manage:

Services and agreements with the propose agreement form

Field Required What to enter / rules
Type Yes (always has a value) agency, supply, profit_share or netting.
Reference Yes Up to 64 characters; must be unique for the company.
Valid from Yes Start date.
Valid to No End date; empty means open-ended. Must not be before Valid from.
Profit share Only for profit_share Percentage above 0 and up to 100, up to 4 decimals (for example 12.5). Ignored for other types.
Settlement currency No Three-letter code, for example USD.
Services No Service codes separated by commas, for example ocean_freight,air_freight. Unknown codes are refused.
Terms No Free text up to 2000 characters.

Decide a pending agreement (row form, posts to /partners/{id}/agreements/{id}/decide): a text box reason labelled "Reason (to reject)" and the buttons Approve and Reject (submitted as decision).

Terminate an approved agreement (row form, posts to /partners/{id}/agreements/{id}/terminate): a date input valid_to labelled "Valid to" (required) and Terminate.

Actions

Button/link What it does Permission Confirmation Result
Statement Opens /partners/{id}/statement partners.statement.view No Partner statement
Add contact /partners/{id}/contacts (POST) partners.partner.update No Contact added; page reloads; audit contact.created
Propose change /partners/{id}/bank-accounts (POST): stores a beneficiary account encrypted, status pending, with an approval request partners.bank.propose No Page reloads; new row pending; audit bank.proposed
Approve (beneficiary row) /bank-accounts/{id}/approve (POST): marks the account verified and any earlier verified account for the partner superseded partners.bank.approve "Approve this beneficiary account? Payments can then be sent to it." Redirects to the Partners list /partners, not back to the partner (D11); audit bank.approved
Save services /partners/{id}/services (POST) partners.profile.update No Returns to #agreements; audit profile.services_updated
Propose agreement /partners/{id}/agreements (POST): new agreement pending partners.agreement.manage No Returns to #agreements; audit agreement.proposed
Approve (agreement row) /partners/{id}/agreements/{id}/decide with decision=approve partners.agreement.approve "Approve this agreement?" Status approved; for agency and profit_share the reference becomes the profile's Agent agreement; audit agreement.approved
Reject Same route with decision=reject partners.agreement.approve "Reject this agreement?" Status rejected, reason shown; audit agreement.rejected
Terminate /partners/{id}/agreements/{id}/terminate (POST) partners.agreement.approve "Terminate this agreement on the chosen date?" Status terminated, Valid to set to the chosen date; audit agreement.terminated

Rules and messages

  • "The partner was not found." (404): the id does not exist or belongs to another company. Staff see the Not found page "That page does not exist or is not available in the active company."
  • Contacts: "Contact name is required."
  • Beneficiary accounts:
  • "Bank name, holder, IBAN and currency are required."
  • "Enter the IBAN or account number as 8 to 34 letters and digits, and a SWIFT code of 8 or 11 characters."
  • "Bank details cannot be stored until the encryption key is configured." (503, Developer-assisted fix: the installation encryption key in the private configuration).
  • Approval by the person who proposed it: "You cannot approve a beneficiary change you submitted." (403, maker-checker).
  • Approving twice or approving an account that is no longer pending: "This beneficiary change is not awaiting approval." (409).
  • Concurrent change: "The bank account was changed by someone else." (409).
  • Services: "The profile was changed by someone else. Reload and try again." (409) when another user saved services first.
  • Agreements:
  • "Enter the agreement type, a reference and a valid date range."
  • "A profit share agreement needs a percentage above 0 and up to 100."
  • "Settlement currency must be a three-letter code."
  • "An agreement with this reference already exists." (409)
  • Rejecting without a reason: "Give a reason for rejecting the agreement."
  • Deciding your own proposal: "You cannot decide an agreement you proposed." (403, maker-checker)
  • "This agreement is not awaiting a decision." (409)
  • Terminate: "Enter a valid end date.", "Only an approved agreement can be terminated." (409), "The end date is before the agreement starts."
  • Supplier payments are refused later with "Beneficiary approval is not valid for this account." when an account is not verified (Chapter 9).

Role differences

Picture not taken (12-partner-bank-pending). The demo has no beneficiary account waiting for approval. The reason is in the screenshot index.

Callout: (1) Approve, shown only to users with partners.bank.approve on a pending account.

  • The contacts add form is shown to everyone who can open the page; only users with partners.partner.update can save it (others get "You do not have permission for this action.").
  • The beneficiary Propose change form, the Approve button, the services form, the propose agreement form and the decide/terminate controls are hidden without their permission.
  • Treasury user holds partners.bank.approve but not partners.partner.view, so the partner detail page answers Not allowed and the Treasury user never sees the Approve button. In the seeded roles the Finance manager is the practical beneficiary approver (see gaps file).

A Treasury user gets Not allowed on the partner page - Bank numbers are always masked on this page, for every role.

Partner statement

Open it with Statement on the partner detail page. Route: /partners/{id}/statement (GET with the from and to query parameters; Arabic title: كشف حساب · {name}).

Partner statement with balances by currency

The page title is "Statement · {name}". The header shows Partner, Roles and Period ("all dates" when no start date). An information notice states "Balances are reported only. Nothing on this page posts or settles." and whether an approved netting agreement is in force. Sections:

  • Balances by currency (الأرصدة حسب العملة): Currency, Invoiced, Credited (net of debit notes), Received, Billed to us, Supplier credits, Paid, Receivable open, Payable open, Net and Position (Partner owes us, We owe the partner, Even). Each currency is a separate row; amounts are never converted.
  • Agreements in force on {date}: approved agreements valid on the To date.
  • Jobs and open claims: the partner's jobs with mode and operations status, and Open claims.
  • Documents: Invoices (receivable) and Supplier bills (payable) with an Open column, Credit and debit notes, Receipts, Supplier credit notes and Payments. Drafts are excluded: receipts count when posted or reversed, and supplier payments count once their batch is released, exported or confirmed.

Fields

Field Required What to enter / rules
From No Start date (YYYY-MM-DD). Empty means all dates.
To No End date. Empty means today (UTC).

Actions

Button/link What it does Permission Confirmation Result
Show Reloads /partners/{id}/statement with the dates partners.statement.view and partners.partner.view No Statement for the period
Print Opens the browser print dialog; the date form is hidden in print none No Printed statement

Rules and messages

  • "Enter valid statement dates." (422)
  • "The start date is after the end date." (422)
  • Without partners.statement.view: "You do not have permission for this action." (403).

Role differences

  • The Statement button is hidden without partners.statement.view.

Imports

Open it from the sidebar: Master data > Imports (الاستيراد). Route: /imports. The upload form posts to /imports.

Imports page with upload form and column templates

Callouts: (1) the Upload form; (2) Columns with the Template buttons; (3) Recent imports.

The subtitle explains the rule: the file is checked as a dry run with the same rules as manual entry, nothing is saved until a person with commit permission commits it, and the commit saves every row or none. The page has three panels:

  • Upload: the form below. While a file uploads, a progress bar and "Uploading … %" are shown; a network failure shows an upload failed message and re-enables the button.
  • Columns (الأعمدة): one row per import type with the Required and Optional columns and a Template download (a CSV header row). The help text: partner roles are separated by semicolons; rate breaks are written as min_quantity:unit_rate separated by semicolons; opening balances cannot use the receivable (1100) or payable (2000) accounts; a file touching another locked account (for example bank and cash) becomes a correction journal.
  • Recent imports (عمليات الاستيراد الأخيرة): the last 100 batches of the company: #, Import, File, Rows, Errors, Status (with the failure code when failed), Uploaded by, Committed by, Created.

Import statuses:

Status Meaning
validated Dry run passed. Nothing has been saved yet.
invalid Dry run found errors. Correct the file and upload it again.
queued Commit is queued and runs with the next scheduler run.
committing Commit is running.
committed All rows were saved.
failed Commit failed and was rolled back. Nothing was saved.
discarded Discarded without saving.

The column lists for each template are in Chapter 18 (file formats).

Fields

The import upload form

Field Required What to enter / rules
Import Yes (always has a value) Partners, Buy rates or Opening balances.
CSV file (UTF-8, up to 1 MB) Yes A .csv file, UTF-8 (a byte order mark is accepted), first row is the header. At most 2000 data rows (200 for opening balances).
Opening balance date Only for opening balances The journal date, YYYY-MM-DD.
Opening balance memo No Up to 255 characters; default "Opening balances import".

Actions

Button/link What it does Permission Confirmation Result
Upload and check /imports (POST): parses the file and runs every row through the normal domain command inside a transaction that is always rolled back data.import.stage (plus the permission manual entry needs for each row) No Redirect to the import detail; audit import.staged
Template Downloads /imports/template/{type}.csv (GET), for example partners_template.csv none beyond being signed in as staff No CSV header row
Batch number link Opens /imports/{id} data.import.stage No Import detail

Rules and messages

Upload refusals (shown on the refusal page; the batch is not stored, a failed import.stage audit event is written):

  • "Choose a CSV file to import."
  • "Choose partners, buy rates or opening balances."
  • "Upload a CSV file of up to 1 MB."
  • "The file must be UTF-8 encoded CSV."
  • "This file was already imported as import #{n} ({status})." (409) when the same file content for the same type is already queued, committing or committed.
  • File structure: "The first row must be the column header.", "Unknown column: … Allowed: …", "A column appears twice in the header.", "Missing required column: …", "Row {n} has {x} cells; the header has {y}.", "A file can hold at most 2000 rows for this import." (200 for opening balances), "The file has no data rows."
  • Opening balances: "Enter the opening balance date as YYYY-MM-DD."; you also need finance.journal.view.

Row errors (stored with the batch, status invalid), for example "Email address is not valid.", "Country code must be two letters.", "Language must be en or ar.", "Unknown partner role: …", "Charge code … is not active.", "Write breaks as min_quantity:unit_rate separated by semicolons.", "Account … does not exist.", "Account … holds open invoices or bills. Opening receivables and payables are entered as documents, not journal lines.", "Debit must be a positive decimal number." Any refusal the domain command raises (for example a duplicate tax ID, or a missing permission) is shown on the row the same way.

Role differences

  • Without data.import.stage the sidebar link is hidden and /imports answers Not allowed.
  • The dry run uses your own permissions: a Company administrator can stage partners, but buy rates need pricing.buy_rate.configure and opening balances need finance.journal.view and finance.journal.draft, so those rows show permission errors for users without them.

Import detail

Open it from Recent imports or automatically after an upload. Route: /imports/{id} (Arabic: الاستيراد رقم {id}: {type}).

Picture not taken (12-import-detail). The demo has no import batch. The reason is in the screenshot index.

Callouts: (1) title, file and status badge; (2) Commit all {n} rows; (3) the Rows table with each row's result.

Picture not taken (12-import-detail-invalid). The demo has no invalid import batch. The reason is in the screenshot index.

The title is "Import #{id}: {type}", with the file name and uploader under it and the status badge. A notice explains the status (see the table above). When the batch failed, a second notice shows the row, failure code and message. Summary shows Uploaded, Rows ("{n} ({errors} with errors)"), the totals of the file (for example totals per currency or whether debits equal credits), the options (opening balance date and memo) and Commit requested by. For opening balances the note says "Committing creates one draft manual journal. A second person approves and posts it from the journals page." Rows lists each row with Row (spreadsheet row number, header is row 1), Result (the error messages in red, OK, or "Saved as {type} #{id}" after commit) and all file columns.

Fields

The two forms on this page have no visible fields. Each carries the hidden version of the batch.

Actions

Button/link What it does Permission Confirmation Result
Discard /imports/{id}/discard (POST), only for validated or invalid batches data.import.stage "Discard this import? Nothing is saved." Status discarded; redirect to /imports; audit import.discarded
Commit all {n} rows /imports/{id}/commit (POST), only for validated batches data.import.commit "Commit all {n} rows? Every row is saved, or none." Status queued; a queue job import.commit is created; the next cron run commits all rows in one transaction under the requester's current permissions; audit import.commit_requested, then import.committed

Staging only: committing a bulk import creates many records at once (partners, buy rates or a draft journal) and cannot be undone from the screen. Test commits in staging.

Rules and messages

  • "Import was not found." (404)
  • "This import changed since you opened it. Reload and try again." (409) when the batch version changed (for example someone else committed or discarded it).
  • "Only a validated import without errors can be committed." (409)
  • "Only an import that has not been committed can be discarded." (409)
  • Commit failures recorded on the batch (status failed, nothing saved): actor_inactive "The person who requested the commit is no longer active in this company.", commit_interrupted "The commit stopped before finishing. Nothing was saved; upload the file again.", or the row and message of the first refused row.
  • The commit runs only when cron runs (Chapter 16). In an environment without cron the batch stays queued.

Role differences

  • Commit all {n} rows is hidden without data.import.commit (AP accountant). Discard is shown to everyone who can open the page.

Reference data

Open it from the sidebar: Master data > Reference data (البيانات المرجعية). Route: /reference.

Reference data catalogues

Three read-only catalogues and one form:

  • Currencies: Code, Name, Minor units (decimal places used for amounts).
  • Incoterms 2020: Code, Name.
  • Charge codes (رموز الرسوم): global codes and the active company's own codes, Code and Description.
  • Add company charge code: shown only with reference.charge.configure; posts to /reference/charge-codes.

Fields

The add company charge code form

Field Required What to enter / rules
Code Yes Short code; stored in lower case (for example docfee). Must be unique for the company.
Description Yes Description shown on rates, quotations and invoices.

A new company charge code is created active, available for buying and selling, category forwarding, for ocean, air and road, unit basis per shipment, and not visible on the portal. These defaults cannot be changed on this screen.

Actions

Button/link What it does Permission Confirmation Result
Add company charge code /reference/charge-codes (POST) reference.charge.configure No Page reloads with the new code

Rules and messages

  • "Code and description are required." (422)
  • Without reference.data.view: "You do not have permission to view reference data." (403; the GET shows Not allowed).
  • Without reference.charge.configure on a direct POST: "You do not have permission to configure charge codes."
  • A code that already exists for the company is refused by the database and shows the generic error page "The request could not be completed." with a reference number (see gaps file).

Role differences

  • Operations and sales roles see the catalogues but not the add form. Pricing officer, Pricing manager and Company administrator see the add form.

Organization

Open it from the sidebar: Administration > Organization (المؤسسة). Route: /organization.

Organization: branches and business calendars

Callouts: (1) the Add branch form; (2) the add holiday form of the first calendar.

The organization page in Arabic

The subtitle: switching a branch never changes the legal company; use the company selector in the top bar for that. The header shows the company Company, Base currency and Time zone; they are read-only here (there is no company edit form). Sections:

  • Branches (الفروع): Code, Name and Business calendar with a select and Save per branch, and the Add branch form in the footer (posts to /organization/branches).
  • Business calendars (تقويمات العمل): one panel per calendar with the working hours form (posts to /organization/calendars/{id}), the Holidays list with Remove per holiday (posts to /organization/calendars/{id}/holidays/remove) and the add holiday form (posts to /organization/calendars/{id}/holidays). The installer creates one calendar per company; there is no screen to add a calendar. Portal enquiry response targets count working hours on the calendar of the shipment's branch: 8 working hours for normal and 2 for urgent enquiries.

Fields

Add branch form:

The add branch form

Field Required What to enter / rules
Branch code Yes Short code; stored in upper case. Must be unique in the company.
Branch name Yes Branch name.

Branch calendar (row form, posts to /organization/branches/{id}/calendar):

Field Required What to enter / rules
Business calendar for No Company default or one of the company's calendars (submitted as business_calendar_id).

Working hours form (one per calendar):

Field Required What to enter / rules
Time zone Yes A valid time zone name, for example Africa/Cairo.
Weekend days No Day numbers separated by commas, 0 for Sunday to 6 for Saturday (Egypt: 5,6). At least two working days must remain.
Day starts Yes 24-hour HH:MM.
Day ends Yes 24-hour HH:MM, after Day starts.

Add holiday form (one per calendar):

Field Required What to enter / rules
Holiday date Yes YYYY-MM-DD; one holiday per date per calendar.
Holiday name Yes 2 to 120 characters.

Actions

Button/link What it does Permission Confirmation Result
Save (branch row) /organization/branches/{id}/calendar (POST) organization.settings.update No Branch uses the chosen calendar; audit branch.calendar_assigned
Add branch /organization/branches (POST) organization.branch.create No New active branch; audit branch.created
Save hours /organization/calendars/{id} (POST) organization.settings.update No Calendar updated; audit calendar.updated
Remove /organization/calendars/{id}/holidays/remove (POST) organization.settings.update "Remove this holiday from the calendar?" Holiday removed; audit calendar.holiday_removed
Add holiday /organization/calendars/{id}/holidays (POST) organization.settings.update No Holiday added; audit calendar.holiday_added

All five actions post to one route family: /organization/calendars/{id}(/holidays|/holidays/remove)?, /organization/branches/{id}/calendar and /organization/branches. Each returns to /organization.

Rules and messages

  • "You do not have permission to add a branch." (403)
  • "Branch code and name are required."
  • "You do not have permission to change organization settings." (403)
  • "Choose a valid time zone, for example Africa/Cairo."
  • "Weekend days are day numbers 0 (Sunday) to 6 (Saturday), separated by commas."
  • "A calendar needs at least two working days."
  • "Working hours use the 24-hour HH:MM format."
  • "The working day must end after it starts."
  • "Holiday date must be YYYY-MM-DD."
  • "Holiday name must be 2 to 120 characters."
  • "That date is already a holiday on this calendar." (409)
  • "Holiday was not found." (404), "Calendar was not found." (404), "Branch was not found." (404)
  • "The company was not found." (404) when the session has no valid company.
  • A duplicate branch code is refused by the database and shows the generic error page (see gaps file).

Role differences

  • D12: /organization (GET) has no permission check. Any signed-in staff user who types the address sees the company, branches, calendars and holidays, and all forms. The sidebar link is shown only to users with an organization.* permission. Every POST is checked, so users without the permission get the refusal message.

Procedures

Create a partner

  1. Open Master data > Partners and click New partner.
  2. Enter Legal name (required) and the optional details.
  3. In Roles, enter the roles separated by commas, for example customer, consignee.
  4. Click Save. The partner detail opens with the new code P-<year>-<number>.

Add a contact

  1. Open the partner detail.
  2. In the Contacts panel footer, enter Name and optionally Email.
  3. Click Add contact.

Propose and approve a beneficiary account (two people)

  1. As the proposer (for example demo-ap@nile.test), open the partner at /partners/{id}.
  2. In Beneficiary accounts, fill Bank, Account holder, IBAN or account number, optionally SWIFT / BIC, and Currency.
  3. Click Propose change. The new row shows pending with the last four characters.
  4. Sign out. Sign in as a different user with partners.bank.approve and partners.partner.view (for example demo-finmgr@nile.test) and open the same partner.
  5. Click Approve on the pending row and confirm "Approve this beneficiary account? Payments can then be sent to it."
  6. The browser returns to the Partners list (D11). Open the partner again: the row is verified, and any older verified account is superseded.

Propose, approve and terminate an agreement

  1. As a user with partners.agreement.manage (Company administrator), open the partner and go to Services and agreements.
  2. In Propose agreement choose Type, enter Reference, Valid from, optionally Valid to, Profit share (for profit share), Settlement currency, Services and Terms.
  3. Click Propose agreement. The row shows pending.
  4. Sign in as a different user with partners.agreement.approve (Finance manager). Click Approve and confirm, or type a reason in "Reason (to reject)" and click Reject.
  5. To end an approved agreement, choose the date in the row's Valid to input and click Terminate; confirm.

View a partner statement

  1. Open the partner and click Statement.
  2. Enter From and To and click Show.
  3. Read Balances by currency and the Position badge. Click Print for a paper copy.

Import partners, buy rates or opening balances

  1. Open Master data > Imports. In Columns, click Template for the import type and fill the file.
  2. Save it as UTF-8 CSV.
  3. In Upload, choose Import, choose the file in CSV file (UTF-8, up to 1 MB) and, for opening balances, enter Opening balance date and optionally Opening balance memo.
  4. Click Upload and check and wait for the progress bar.
  5. On the import detail read the notice and the Rows table. If the status is invalid, correct the rows shown in red, then Discard the batch and upload the corrected file.
  6. If the status is validated, a user with data.import.commit clicks Commit all {n} rows and confirms.
  7. The status becomes queued. After the next cron run, reload: the status is committed and each row shows "Saved as {type} #{id}". For opening balances, a second person approves the draft journal on the journals page (Chapter 10).

Add a company charge code

  1. Open Master data > Reference data.
  2. In Add company charge code, enter Code and Description.
  3. Click Add company charge code.

Maintain branches and calendars

  1. Open Administration > Organization.
  2. To add a branch, enter Branch code and Branch name and click Add branch.
  3. To change a branch calendar, choose the calendar in the branch row and click Save.
  4. To change working hours, edit Time zone, Weekend days, Day starts and Day ends and click Save hours.
  5. To add a holiday, enter Holiday date and Holiday name and click Add holiday. To remove one, click Remove and confirm.

Troubleshooting

Symptom Cause What to do
After approving a bank account you land on the Partners list Known defect D11 Open the partner again to see the verified row.
"You cannot approve a beneficiary change you submitted." Maker-checker: the proposer cannot approve Ask a second user with partners.bank.approve.
Treasury user cannot find the Approve button The role lacks partners.partner.view, so the partner page is not allowed Use a Finance manager, or ask an administrator to review the role design (gaps file).
"Bank details cannot be stored until the encryption key is configured." No valid encryption key in the private configuration Developer-assisted: configure security.encryption_key.
"A partner with this tax ID already exists: P-…" Tax IDs are unique across the installation Open that partner instead of creating a new one.
"You cannot decide an agreement you proposed." Maker-checker A second user decides.
Import stays queued Cron has not run Developer-assisted: check the Scheduler panel on /admin/health and the cron entry.
"This file was already imported as import #…" Same file content for the same type is queued or committed Open that import; change the file only if you really need a second import.
Import rows show permission errors The dry run uses your own permissions Ask a user with the right permissions, or have the permission added.
"This import changed since you opened it. Reload and try again." Another user acted on the batch Reload the page and check the status.
"The request could not be completed." when adding a branch or charge code The code already exists (database unique key) Use a different code; report the generic message (gaps file).
"The form token is not valid. Reload the page and try again." (Form expired, 419) The page was open too long or in another session Reload the page and submit again.
"Writes are paused while a backup runs. Try again in a few minutes." A backup or restore holds the maintenance gate Wait, or ask the Platform administrator (Chapter 13).

13. Administration: users, audit, health, integrations, backups and upgrade

This chapter covers the screens that administrators use to run the installation: users and role assignment, the audit log and its integrity check, system health, API credentials for machine access, provider integrations, encrypted backups, the browser upgrade and the browser installer. References: Spec §19 (users, roles, permissions and audit), §22, §24 (APIs and integrations), §25 (security), §26 (deployment and maintenance); UML 02, 03, 23, 24, 25, 28, 38, 41, 47, 48, 49, 50, 51.

Who uses it (seeded roles):

Screen Roles Permission keys
Users and roles Security administrator identity.user.list, identity.user.create, system.role.assign
Audit log Security administrator, Platform administrator, Finance manager, Pricing manager, Auditor (view); Security administrator (export) system.audit.view, system.audit.export; system.health.view widens the scope to installation events
System health Platform administrator (clear cache); see Role differences for who can open the page system.health.view, system.cache.clear
API credentials Platform administrator, Company administrator system.api.manage
Integrations Platform administrator (view and provider calls), Company administrator (view) system.integration.view, system.integration.manage
Backups and Upgrade Platform administrator system.backup.view, system.backup.run
Installer The person holding the one-time setup secret on the server none (no user exists yet)

Demo accounts: admin@nile.test (Platform, Security and Company administrator) and browser-staff@nile.test (Platform administrator, Company administrator and Finance manager) open every screen here; demo-security@nile.test and demo-auditor@nile.test show the narrower views.

No live provider is configured in this environment. The local configuration binds no integration profile, the mail transport is unconfigured and backups are not scheduled. Nothing in this chapter claims that a provider call, e-mail or off-server copy succeeded. For live checks follow docs/LIVE_PROVIDER_CHECKLIST.md. Operator procedures are in docs/MAINTENANCE_RUNBOOK.md, docs/DEPLOYMENT_GUIDE.md and docs/HOST_CHECKLIST.md.

Users and roles

Open it from the sidebar: Administration > Users and roles (المستخدمون والأدوار). Route: /admin/users. The page title is Users (المستخدمون).

Users and roles

Users and roles on a phone screen

The Add user panel creates an account. The Users table lists the members of the active company with Name, Email, Type (Internal or External), Status and Role (a role select with Assign). The table does not show the roles a user already has or their scopes, although the subtitle says "Staff and external accounts with their role assignments." The filter box filters only the rows on the page.

Fields

Add user form:

The add user form

Field Required What to enter / rules
Name Yes Display name, up to 160 characters.
Email Yes A valid e-mail address, unique in the installation.
Password Yes At least 12 characters with a letter and a number. Give it to the user through a separate channel; never write it in tickets or this manual.
Type Yes (always has a value) Internal (staff) or External (portal user).

Role row form: the select role_id (labelled "Role for {name}" for screen readers) lists the template roles.

Actions

Button/link What it does Permission Confirmation Result
Add /admin/users (POST): creates the user with an active membership in the active company identity.user.create No Page reloads; audit user.created
Assign /admin/users/{id}/roles (POST): adds the role for the active company system.role.assign and a delegation for that role No Page reloads; audit role.assigned. Assigning a role does not ask for a second sign-in code.

Rules and messages

  • "Email is not valid."
  • "Use at least 12 characters with a letter and a number."
  • "Account type is not valid."
  • "A user with this email already exists." (422)
  • "This role is outside your delegated authority." (403): you may assign only roles delegated to you. The installer delegates every role to the first administrator; there is no screen to delegate roles to other administrators.
  • "The user or role was not found." (404), "The user was not found in this company." (404)
  • "An external role cannot be assigned to an internal user." / "An internal role cannot be assigned to an external user." (422)
  • Without the permission: "You do not have permission for this action." (403). Without identity.user.list the GET shows Not allowed.

Role differences

  • D13: there is no route to suspend a user, remove a role or set a data scope from this screen. identity.user.suspend exists as a permission only. Removing access needs developer help.
  • Company administrator does not hold identity.user.list; only the Security administrator (and admin@nile.test) can open the page.

A Company administrator gets Not allowed on Users and roles

Audit log

Open it from the sidebar: Administration > Audit log (سجل التدقيق). Route: /admin/audit (GET with filters). Page title Audit (التدقيق).

Audit log with integrity panel and filters

Callouts: (1) the Integrity panel with Verify next batch now; (2) Filter events; (3) the JSON detail of the first event.

The subtitle states the scope: "Showing {scope} events. Cost, margin and bank fields you may not see are shown as [restricted]. Opening this page and exporting are recorded." The scope is "company" or, for users with system.health.view, "company and installation". Panels:

  • Integrity (السلامة): Result (verified or failed), Last check, Sequences, Content-verified, Links only (older events), Changed or unlinked, Missing ranges, Checkpoints without a key, "First problem at sequence {n}." when failed, and "Last signed checkpoint at sequence {n}" (or "No signed checkpoint yet."). Before the first verification: "The audit chain has not been verified yet. Cron verifies it in batches."
  • Filter events (تصفية الأحداث): the filter form.
  • Events: newest first, 50 per page, with module, action, outcome badge, actor, entity, time ("Times shown in {tz}"), request id, reason for failures, a change table (Field, Change = added / edited / removed, Before, After) and a JSON detail (تفاصيل JSON) disclosure with the full redacted event. Values you may not see are [restricted]; secrets are always [redacted]. Audit pages pager at the bottom. "No events match." when empty.

Fields

The audit filter form

Field Required What to enter / rules
From (UTC) No Date YYYY-MM-DD.
To (UTC) No Date YYYY-MM-DD.
User (name or id) No Part of the actor name or a user id.
Outcome No Any, Succeeded or Failed.
Module No Module code, for example partners, finance, system.
Action No Action code, for example bank.approved.
Entity type No For example partner_bank_account.
Entity id No A number.
Request id No The correlation id shown on error pages ("Reference …") and in the X-Correlation-Id header.

Actions

Button/link What it does Permission Confirmation Result
Filter Reloads /admin/audit with the filters system.audit.view No Filtered events; audit audit.viewed
Clear Removes the filters system.audit.view No All events in scope
Export CSV (تصدير CSV) Downloads /admin/audit?export=1&… as audit.csv (up to 5000 rows, same filters) system.audit.export No CSV file; audit audit.exported (refusals are recorded as audit.export_refused)
Verify next batch now (تحقق من الدفعة التالية الآن) /admin/audit/verify (POST): checks the next 5000 events of the hash chain and the signed checkpoints system.audit.view No Integrity panel updated; audit audit.integrity_verified
JSON detail Expands the event JSON system.audit.view No Read-only

CSV columns: occurred_at_utc, sequence, company_id, actor_type, actor, module, action, entity_type, entity_id, outcome, reason, correlation_id, channel, changes_json. Text cells that start with =, +, - or @ are prefixed with an apostrophe so spreadsheets do not run them as formulas.

Rules and messages

  • "You do not have permission to view the audit log." (403)
  • "Export permission is separate from view permission." (403) for Export CSV without system.audit.export.
  • "Dates use the form YYYY-MM-DD." (422), "The entity id is a number." (422)
  • "You do not have permission to verify the audit log." (403)
  • A failed verification also turns the Audit and security health panel critical: "Audit chain verification FAILED at sequence …". Do not edit or delete audit rows; follow the runbook section "Audit integrity alert".

Role differences

  • Without pricing.buy_rate.view, cost, buy, margin, profit, commission and accrual fields show [restricted].
  • Without partners.bank.approve, IBAN, SWIFT, BIC and account number fields show [restricted].
  • Users with system.health.view also see installation events that have no company (backups, upgrades, installer).
  • Auditor, Finance manager and Pricing manager can view but not export.

The audit log as an Auditor sees it

System health

Open it from the sidebar: Administration > System health (صحة النظام). Route: /admin/health.

System health panels

Callouts: (1) Scheduler; (2) Backups and restores; (3) Clear file cache.

System health in Arabic

The subtitle: "Unknown means the measurement is not available. It is not a healthy result." Each panel shows a severity badge (ok, warning, critical or unknown), the time it was checked, its scope and an action link for users allowed to act:

Panel What it reports Link
Tasks and integrations Pending and failed queue jobs (installation); for the active company, unknown outcomes to reconcile and failed or rejected provider calls in the last 24 hours Open integrations (system.integration.view)
Scheduler "No scheduler heartbeat has been recorded. Cron may not be set up." or the last heartbeat and its age; a lease left by a stopped run; the counts of overlapping runs refused and leases taken over none
Disk and files Whether the cache directory is writable; free disk space against the minimum (1 GB by default). Critical below the minimum: "Backups will refuse to start until space is freed." Review backup archives when critical
Backups and restores Maintenance gate state, last verified backup and its age (overdue after 24 h without a schedule), off-server copy and key copy confirmation, last restore. RPO and RTO are not set, so no recovery target is marked satisfied. Open backups (system.backup.view)
Runtime and database PHP version and database server version none
Audit and security Private storage present, emergency audit spool, last chain verification, missing checkpoint key Open the audit log (system.audit.view) when not ok

Fields

The page has no input fields.

Actions

Button/link What it does Permission Confirmation Result
Clear file cache (مسح ذاكرة التخزين المؤقت للملفات) /admin/cache/clear (POST): empties storage/cache system.cache.clear "Clear the file cache? Cached data is rebuilt on the next requests." Returns to /admin/health. Documents, sessions and business records are not touched.
Panel links Open the related admin page as listed No Navigation

Rules and messages

  • "You do not have permission to clear the cache." (403)
  • The Clear file cache button appears only with system.cache.clear.

Role differences

  • The sidebar link is shown only with a system.health.* permission, but /admin/health itself has no permission check: any signed-in staff user who types the address sees installation-wide queue counts, disk space, backup state and server versions (see gaps file).

API credentials

Open it from the sidebar: Administration > API credentials (بيانات اعتماد الواجهة البرمجية). Route: /admin/api-credentials (GET lists, POST issues).

API credentials with the issue form

Subtitle: "Machine access to /api/v1 for this company. Each credential has scopes, a per-minute limit and an expiry." The header links OpenAPI description (/api/v1/openapi.json). The Credentials table shows Name, Prefix (the fak_… prefix, never the secret), Scopes, Limit/min, Expires, Last used (never when unused), State (active, expired or revoked) and Revoke. "None issued." when empty.

After Issue credential, a warning notice shows once: "Copy this token now. It is not shown again." with the full token, a Copy button and "Send it as Authorization: Bearer . Writes also need an Idempotency-Key header." Only a SHA-256 hash of the token is stored. The response is not cached by the browser. Screenshots of this notice must mask the token.

Fields

The issue credential form

Field Required What to enter / rules
Integration name Yes Who or what uses it, up to 120 characters.
Expiry Yes A date after today and within a year (366 days).
Requests per minute No 1 to 600; default 60.
Scopes (scopes[] checkboxes) At least one partners.read (List and read partners and contacts (no bank details)), partners.write (Create partners (idempotent)), jobs.read (List and read jobs, shipments and milestones (no costs or margins)), invoices.read (List posted customer invoices and open balances), tracking.read (Read normalized carrier tracking events).

Actions

Button/link What it does Permission Confirmation Result
Issue credential (إصدار بيانات الاعتماد) /admin/api-credentials (POST) system.api.manage No One-time token notice; audit api_credential.issued (without the token)
Revoke (إلغاء) /admin/api-credentials/{id}/revoke (POST) system.api.manage "Revoke this credential? Calls using it stop working at once." State revoked; audit api_credential.revoked
OpenAPI description Opens /api/v1/openapi.json none (public) No JSON description

Rules and messages

  • "Name the integration (up to 120 characters)."
  • "Choose at least one known scope."
  • "Choose an expiry date after today and within a year."
  • "The rate limit must be between 1 and 600 requests a minute."
  • "No active credential was found." (404) when revoking a revoked, expired or other company's credential.
  • Calls made with a credential act as the user who issued it, within the chosen scopes (Chapter 18).

Role differences

  • Company administrator and Platform administrator can issue and revoke. Others get Not allowed.

Integrations

Open it from the sidebar: Administration > Integrations (التكاملات). Route: /integrations.

Integrations with no provider bound

Sections:

  • Bound profiles (الملفات المرتبطة): Provider, Profile, Environment (fake is shown as "not a provider"), Credential, Host, Secrets present, State (Usable or the problem; the webhook address for tracking). With nothing bound: "No provider profile is bound to this company." and "Profiles and bindings are set in the private config file (integrations.profiles and integrations.bindings)." This is the state of this environment.
  • Run a provider call (تنفيذ استدعاء لدى المزود), only with system.integration.manage: Fetch tracking for a reference, Submit a posted invoice for tax, Payment link for an open invoice, with the note "A payment link asks for payment; money is recorded only by a receipt posted from the bank statement. A tax rejection does not change the invoice."
  • Tracking state, Latest events, Inbound webhooks (خطافات الويب الواردة; signature, outcome, effect, bytes) and Outbound attempts (المحاولات الصادرة; provider, operation, outcome, HTTP status, provider code, reference). An attempt with an unknown outcome has a reconcile form.

After a provider call the page shows the outcome as a notice instead of an error page.

Fields

Field Required What to enter / rules
reference (placeholder "Container, B/L, AWB or job") Yes Container number, bill of lading, air waybill or job reference to fetch.
invoice_id (placeholder "Invoice id") Yes Id of a posted invoice, for Submit and Create link.
Provider answer (decision) Yes "Provider says…", then Not received or Received.
external_ref (placeholder "Provider reference") When Received The reference the provider gave for the received submission.
note (placeholder "How it was confirmed") Yes How you confirmed the outcome with the provider, up to 500 characters.

Actions

Button/link What it does Permission Confirmation Result
Fetch (جلب) /integrations/tracking/fetch (POST) system.integration.manage No Outcome notice; attempt recorded
Submit (إرسال) /integrations/tax/submit (POST) system.integration.manage "Submit this invoice to the tax provider?" Outcome notice; attempt recorded
Create link (إنشاء الرابط) /integrations/payments/link (POST) system.integration.manage No Outcome notice; attempt recorded
Record (تسجيل) /integrations/attempts/{id}/reconcile (POST): records what the provider said about an unknown outcome system.integration.manage "Record this provider answer? It settles the unknown outcome." Attempt settled; audit integration.outcome_reconciled

The single coverage action for this screen is /integrations (ACT-144); the three provider calls and the reconcile route are listed above.

Rules and messages

Outcomes shown as notices (in this environment the first one is expected):

  • "This integration is not configured for the company. …" (503): no profile bound. This is not a failure of the provider and nothing was sent.
  • "A fake integration profile cannot run in this environment." (503) outside local, development and test.
  • "The provider did not answer in time, so the outcome is unknown. Check with the provider before trying again; a retry reuses the same idempotency key." (504)
  • "The provider could not be reached. Nothing was sent." (502)
  • "The provider refused the request (HTTP …)." (502)
  • "The provider answered in an unexpected format, so the outcome is unknown." (502)
  • "The last attempt has an unknown outcome. Check with the provider and record whether it was received before sending again." (409)
  • "Another request is sending this submission now. Wait for its outcome." (409)
  • Tax: "Invoice was not found." (404), "Only a posted invoice can be submitted to the tax authority." (409), "The tax profile has no api_key secret." (503)
  • Tracking: "Enter a container, bill of lading, air waybill or job reference." (422), "The tracking profile has no api_key secret." (503)
  • Payment link: "Only a posted invoice with an open amount can get a payment link." (409), "The payment profile has no api_key secret." (503)
  • Reconcile: "Say how the outcome was confirmed with the provider (up to 500 characters).", "Attempt was not found." (404), "Only an unknown outcome sent under an idempotency key can be reconciled." (409), "A later attempt or decision exists for this submission." (409), "Enter the reference the provider gave for the received submission.", "Choose received or not received."

Role differences

  • Without system.integration.view the page answers "You do not have permission for this action." (403).
  • Company administrator sees the page but not Run a provider call or the reconcile forms.

Integrations as a Company administrator sees them

Backups

Open it from the sidebar: Administration > Backups (النسخ الاحتياطية). Route: /admin/backups.

Not allowed on Backups for the signed-in demo account

The picture is the Not allowed page. The panels below are what the screen contains for an account with system.backup.view.

Panels:

  • Maintenance gate (بوابة الصيانة): Writes open ("Writes are open. Cron work runs.") or Writes paused ("Writes are paused since …", with "Run {id} is in progress", trigger, step and resume count). "A run pauses writes until it has exported every table. Releasing the gate during a run ends that run as failed."
  • Encryption key (مفتاح التشفير): Key id, Fingerprint (not yet created before the first run), "The key is kept on this server, outside every archive." and the key copy state ("No off-server copy of this key is confirmed." or who confirmed it and where). "If this server is lost, no archive can be decrypted. …"
  • Schedule and retention: the schedule (not scheduled: "Backups run only from this page. Set backup.schedule in the configuration (hourly, every_6_hours, every_12_hours or daily) to run them from cron."), Next due, the retention rule ("Retention keeps the newest run of each of the last {days} days, {weeks} weeks and {months} months, and always the newest verified archive.") and the preview ("Applying it now would delete {n} archive(s) (runs …)." or "Nothing would be deleted now.").
  • Backup actions (only with system.backup.run): the buttons and forms below.
  • Recent runs (التشغيلات الأخيرة): Run, Status (running, verified, failed, pruned shown as "Deleted by retention"), Trigger, Started, Verified, Archive (bytes), Tables, Rows, Files, Off-server copy (Download archive link plus the copy confirmation form, or "Off-server copy confirmed by {name}") and Reason (failure code). "No backup has run yet." when empty.
  • Quarantined after restore (معزول بعد الاستعادة): on a restored copy, the quarantined queue jobs with Return to pending, and the counts "{n} email notice(s) cancelled by restore. They are not sent again from this page." and "{n} outbox event(s) closed by restore. They are not dispatched again from this page." Otherwise "Nothing is quarantined."

Fields

Field Required What to enter / rules
Your password Yes, for Download the key Your own sign-in password; checked again before the key is released.
Where the copy is kept and who holds it Yes, for Confirm key copy Up to 255 characters, for example "Password manager vault Finance, held by two named people".
note (placeholder "Where the archive copy is kept") Yes, for Confirm on a run Up to 255 characters.

Actions

Staging only: Run encrypted backup, Release maintenance gate, Apply retention now and Return to pending pause writes, delete archives or resend work. Run them in staging unless you are the operator of a live installation following the runbook.

Picture not taken (13-backups-actions). The signed-in account was refused on Backups, so the actions panel was not on the page. The reason is in the screenshot index.

Button/link What it does Permission Confirmation Result
Run encrypted backup (تشغيل نسخة احتياطية مشفرة) /admin/backups (POST): engages the gate, exports every table and private file into an encrypted, verified archive, then releases the gate system.backup.run "Run an encrypted backup now? Writes pause until every table is exported." New verified run; audit backup events. Disabled while the gate is engaged.
Release maintenance gate (تحرير بوابة الصيانة) /admin/backups/gate/release (POST); shown only while engaged system.backup.run "Release the maintenance gate? A run in progress ends as failed, and quarantined work is not resent." Writes resume; a running backup ends failed with gate_released_by_administrator; audit backup.gate_released
Apply retention now (تطبيق الاحتفاظ الآن) /admin/backups/retention (POST) system.backup.run "Apply retention now? Archives outside the retention rule are deleted." Archives outside the rule deleted, runs pruned; audit backup.retention_applied. Disabled when nothing would be deleted.
Download archive (تنزيل الأرشيف) /admin/backups/runs/{id}/download (GET): the encrypted .ferpbak file; shown for verified runs system.backup.run No File download after its checksum is re-checked; audit backup.downloaded
Confirm (run row) /admin/backups/runs/{id}/copied (POST) with note system.backup.run No "Off-server copy confirmed by {name}"
Download the key (تنزيل المفتاح) /admin/backups/key/download (POST) with Your password system.backup.run No Text file ferp-backup-key.txt with the key id, fingerprint and key; audit backup.key_downloaded
Confirm key copy (تأكيد نسخة المفتاح) /admin/backups/key/copied (POST) system.backup.run No Key copy confirmed on the page and on health
Return to pending (إعادة إلى الانتظار) /admin/backups/jobs/{id}/return (POST) system.backup.run "Return this job to pending? Cron runs it after the gate is released." Job pending; audit backup.job_returned

Rules and messages

  • While the gate is engaged, every form that writes (on any screen) answers with a plain-text page "Writes are paused while a backup runs. Try again in a few minutes." (HTTP 503, Retry-After 60). Only Release maintenance gate is allowed. On a restored staging copy, sign-in, sign-out and Return to pending are also allowed (D19).
  • "You do not have permission for this backup action." (403)
  • "There is not enough free disk space for a backup (… bytes free, about … needed). Nothing was paused." (507)
  • "The backup did not start. Writes have resumed. See the run record for the reason." (500)
  • "The backup did not complete. Writes have resumed. See the run record for the reason." (500)
  • "No verified archive is stored for this run." (404), "The archive file is missing or no longer matches its recorded checksum." (409)
  • "Say where the copy is kept (up to 255 characters)." (422), "Only a verified run can be marked as copied." (404)
  • "The password is not correct." (403)
  • "Say where the key copy is kept and who holds it (up to 255 characters)." (422), "The key is created on the first backup. Run a backup first." (409)
  • "Queue job was not found." (404), "Only a quarantined job can be returned to pending." (409)

Role differences

  • Without system.backup.view the page is Not allowed. Without system.backup.run the Backup actions panel, Download archive links, copy confirmation forms and Return to pending are hidden. View-only users see the panels, the runs and the retention preview.

Upgrade

Open it from the sidebar: Administration > Upgrade (الترقية). Route: /admin/upgrade.

Not allowed on Upgrade for the signed-in demo account

The picture is the Not allowed page. The rest of this section is what the screen contains for an account with system.backup.view.

Pending migrations (الترحيلات المعلقة) lists the migration files in the uploaded release that the database has not applied (Migration). When nothing is pending: "The database matches this application. There is nothing to apply." After an apply the page shows "{n} migration(s) applied."

The upgrade does not take a backup by itself. Run a verified backup and confirm its off-server copy first (DEPLOYMENT_GUIDE "Upgrade", step 1).

Fields

The page has no input fields.

Actions

Staging only: rehearse every upgrade on a restored staging copy before the live installation.

Button/link What it does Permission Confirmation Result
Apply pending migrations (تطبيق الترحيلات المعلقة) /admin/upgrade (POST): engages the gate, applies the migrations in order, reruns the essential seed (adds missing permissions to roles), releases the gate system.backup.run "Apply {n} pending migrations? Writes pause while they run." Redirect to /admin/upgrade?applied={n}; audit upgrade.applied

Rules and messages

  • "You cannot do that." (403) without the permission.
  • A failed migration stays pending, the gate is released and a failed upgrade.apply audit event is recorded. Fix the cause and apply again; applied migrations are never rerun.
  • During the apply, other users get the 503 "Writes are paused …" page.

Role differences

  • system.backup.view opens the list; only system.backup.run shows the button.

Installer

There is no sidebar link. Open /install on a new upload (every page redirects there until the installation is locked). After installation /install redirects to /dashboard. Title: Install Freight ERP (تثبيت Freight ERP).

Installer steps

Staging only: the installer runs on a fresh copy only. Never place a setup secret on a live installation.

Steps (Installation steps): Setup secret, Environment, Database, Company and administrator.

  1. Setup secret: if storage/private/setup.secret is missing: "Create storage/private/setup.secret on the server with a one-time setup secret, then enter it here. The first visitor cannot claim the installation without that file." Posts to /install/unlock.
  2. Environment: each check (PHP version and extensions, writable paths) with ok, unknown or a failure badge.
  3. Database: posts to /install/database; tests the connection and writes the private configuration (app/config.php). Connection saved badge afterwards. The tables are created in the last step.
  4. Restore from an encrypted backup (optional, after the database is saved): "Optional. Use it instead of the next step to start from an existing installation." Posts to /install/restore (Chapter 16, REC-02).
  5. Company and administrator: posts to /install/finish; runs the migrations and essential seed, creates the first company and the administrator (with every role delegated) and locks the installer.

Fields

Field Required What to enter / rules
Setup secret Yes The value in storage/private/setup.secret. Never write it in tickets or this manual.
Host Yes Pre-filled 127.0.0.1 (on Hostinger shared hosting use 127.0.0.1 or localhost).
Port Yes Pre-filled 3306.
Database name Yes An existing, empty database; CREATE DATABASE privilege is not needed.
Username Yes Database user.
Password No Database password.
Table prefix No Optional prefix for every table.
Archive Yes, for restore A .ferpbak file in storage/backups.
Staging URL Yes, for restore "The staging URL must differ from the source installation's URL."
System name Yes Pre-filled "Freight ERP".
Base URL Yes The public URL of this installation.
Company name Yes Legal name of the first company.
Code Yes 2 to 16 letters or digits; converted to upper case.
Base currency Yes Pre-filled EGP; a seeded currency such as EGP, USD, EUR or AED.
Time zone Yes Pre-filled Africa/Cairo.
Administrator name Yes First administrator's display name.
Administrator email Yes First administrator's e-mail.
Password (admin_password) Yes At least 12 characters with a letter and a number.

Actions

Button/link What it does Permission Confirmation Result
Continue /install/unlock (POST) setup secret No Environment step
Test and save connection /install/database (POST) unlocked session No Connection saved
Restore into this database /install/restore (POST) unlocked session "Restore this archive into the saved database? The installation becomes restored staging." Restored staging copy; redirect to sign-in
Install /install/finish (POST) unlocked session No Installer locked; redirect to /login

Rules and messages

  • "The setup secret is not valid." (403)
  • "PHP 8.1.34 with mysqlnd 8.1.34 is required before the database can be saved." and "… before installation can finish." (422; the version text is fixed in the code)
  • "Save the database connection first."
  • "Company code must be 2 to 16 letters or digits."
  • "Use at least 12 characters with a letter and a number."
  • "Choose a seeded currency such as EGP, USD, EUR or AED."
  • D20: installer errors are shown on the page with HTTP 200 and the raw error text (for example a database driver message or a restore code such as restore_target_not_empty), and the Company and administrator form is visible before the database connection is saved.

Role differences

  • No roles exist yet. Possession of the setup secret is the only control.

Procedures

Add a user and assign a role

  1. Sign in as a Security administrator (demo-security@nile.test) and open Administration > Users and roles.
  2. In Add user enter Name, Email, a Password that meets the policy and Type; click Add.
  3. In the user's row choose the role and click Assign. Repeat for each role.
  4. Tell the user to sign in. A correct password opens the dashboard.

Investigate an event in the audit log

  1. Open Administration > Audit log.
  2. Enter the Request id from the error page ("Reference …"), or a Module, Action, Entity type and Entity id, and click Filter.
  3. Open JSON detail for the full event. With export permission click Export CSV.

Verify the audit chain

  1. Open the audit log and read the Integrity panel.
  2. Click Verify next batch now. Repeat until Sequences reaches the latest event.
  3. If Result is failed, follow the runbook "Audit integrity alert". Do not change audit rows.

Issue and revoke an API credential

  1. Open Administration > API credentials.
  2. Enter Integration name, Expiry, Requests per minute and tick the Scopes; click Issue credential.
  3. Copy the token immediately with Copy and give it to the integrator through a secure channel. It is not shown again.
  4. To stop it, click Revoke in its row and confirm.

Reconcile an unknown provider outcome (Developer-assisted with the provider)

  1. Open Administration > Integrations; find the attempt with outcome unknown in Outbound attempts.
  2. Check with the provider whether it received the submission.
  3. Choose Provider answer, enter Provider reference when received and How it was confirmed; click Record and confirm.

Take a backup and confirm the copies (Staging only)

  1. Open Administration > Backups. Check Disk and files on health first.
  2. Click Run encrypted backup and confirm. Wait until the run is verified.
  3. Click Download archive and store the file off the server; enter where it is kept in the run's note and click Confirm.
  4. Enter Your password and click Download the key. Store the key apart from the archives; enter where in Where the copy is kept and who holds it and click Confirm key copy.

Apply an upgrade (Staging only first)

  1. Take a verified backup and confirm its off-server copy.
  2. Upload the new release, keeping app/config.php and storage/.
  3. Open Administration > Upgrade, review Pending migrations and click Apply pending migrations; confirm.
  4. Open System health and check every panel.

Install a new copy (Staging only)

  1. Create storage/private/setup.secret on the server (Developer-assisted, through the hosting file manager).
  2. Open /install, enter the Setup secret, review Environment.
  3. Enter the database settings and click Test and save connection.
  4. Fill Company and administrator and click Install. Sign in at /login.

Troubleshooting

Symptom Cause What to do
"This role is outside your delegated authority." No delegation for that role Ask the first administrator; delegations have no screen (Developer-assisted).
You need to suspend a user or remove a role Not available (D13) Developer-assisted; record the request.
Audit values show [restricted] Your role lacks the cost or bank permission Expected. Ask a user with the permission.
"Export permission is separate from view permission." You have view only Ask a Security administrator.
Scheduler panel unknown No cron heartbeat yet Developer-assisted: set up cron (Chapter 16, SCH-01).
"This integration is not configured for the company. …" No profile bound (this environment) Expected here; for live use follow the live provider checklist.
Every save answers "Writes are paused while a backup runs. Try again in a few minutes." Maintenance gate engaged Wait; if no run is progressing, a Platform administrator uses Release maintenance gate.
"There is not enough free disk space for a backup …" Disk below the threshold Free space or apply retention, then retry.
"The password is not correct." on key download Wrong password Enter your own sign-in password.
Upgrade still lists a migration after apply The migration failed Read the audit event upgrade.apply (failed) and fix the cause (Developer-assisted).
Installer shows a raw database message D20 Correct the database settings; report the raw text to the developer, not to end users.

14. Portal administration (staff)

Staff use these screens to decide exactly what an external customer or agent can see in the customer portal. A portal organization stands for one partner profile of one type (customer or agent) in the active company. Staff verify the partner's contacts, invite people, share individual shipments, publish individual milestones and documents, answer portal enquiries and review the files customers upload.

Who uses it: in the seeded roles only Company administrator holds the staff portal permissions. The demo users with that role are browser-staff@nile.test and demo-companyadmin@nile.test. The permission keys are:

Permission key What it unlocks
portal.organization.manage Organization list, create, open the organization page, suspend or reinstate, portal policy, member access switches (Billing, Export), notification counts, member requests panel
portal.member.invite Verify and withdraw contacts, create invitation links, approve "add a colleague" requests
portal.member.suspend Suspend, revoke and reinstate memberships, member access switches, approve "remove a colleague" requests
portal.shipment.grant Share a shipment, change scope, revoke a share
portal.content.publish Publish and unpublish milestones, publish and withdraw documents
portal.audience.preview Preview what this organization sees
portal.enquiry.manage Portal enquiries queue and enquiry page (reply, assign, status, priority, change request decision)
portal.upload.review Customer uploads queue, download to inspect, approve, reject, request and cancel document requests

Spec §33.2, §33.3, §33.6, §33.12. UML 21, 22, 39, 40, 46.

How portal visibility works: explicit grants and publication

Nothing is visible to a portal user by default (Spec §33.3, §33.6). The screen subtitle says so: "Portal organizations give an external customer or agent access to only the shipments granted to them, and only the milestones published to them. Nothing is visible to a portal user by default."

A portal user sees a piece of shipment information only when every one of these is true:

  1. The user's account is an active external account, and the membership in this organization is Active.
  2. The portal organization is Active (not suspended) and the serving company is active.
  3. The shipment has an active share (grant) for this organization, and its "Valid through" date has not passed. The expiry is checked when each page is read, so an expired share stops working at once.
  4. For the timeline: the milestone is recorded and has been published to this organization.
  5. For a document: the stored file is available (not quarantined) and has been published to this organization for this shipment.
  6. For route, cargo and carrier references: the share includes that field group. Route and Cargo are on by default; Carrier references are off until staff tick them.
  7. For a split shipment: the share covers the lot (no lot ticked means every lot).

Sharing a shipment does not publish its milestones or documents. Publishing a milestone or a document needs an active share first. Revoking a share also withdraws, in the same transaction, every published milestone and document of that shipment for this organization, and cancels queued notifications. A shipment that is not shared shows the portal user the same "Not found" page as a shipment that does not exist (P01).

Approving a customer upload does not publish it. Internal notes, internal files and unpublished milestones never reach the portal (P10, P20).

Portal administration

Open it from the sidebar: Administration > Customer portal (الإدارة > بوابة العملاء). Route: /admin/portal. The fact pack also lists the list route as /admin/portal (POST); the screen itself is a GET page.

Portal organizations list with the create form

Title "Customer and agent portal" (بوابة العملاء والوكلاء). The header has two buttons: Portal enquiries and Portal uploads.

Organizations (المؤسسات) table, one row per portal organization of the active company:

Column Meaning
Name The partner's legal name (copied when the organization was created). Link opens the organization page.
Type Customer or Agent.
Status Active: members can sign in. Suspended: every membership of the organization is blocked.
Active members Memberships with status Active.
Shared shipments Shares that are active and not expired.
Manage Opens the organization page.

When the list is empty: "No portal organization yet." and "Create one for an active partner profile with the form on this page."

Create an organization (إنشاء مؤسسة) panel. If the company has no active partner profile it shows "Create an active partner profile first. Each organization is one partner profile of one type." with a New partner button.

Create an organization form

Fields

Form POST /admin/portal/organizations:

Field Required What to enter / rules
Partner profile Yes An active partner profile of the active company. The list shows legal names.
Type Yes Customer or Agent. One partner profile can have one organization of each type.

Actions

Button/link What it does Permission Confirmation Result
Create organization Creates the organization through /admin/portal/organizations. Status Active, language English, time zone of the company. portal.organization.manage None Opens the new organization page. Audit portal.organization.created.
Manage / name link Opens /admin/portal/organizations/{id} portal.organization.manage None Organization page
Portal enquiries Opens /admin/portal/enquiries portal.enquiry.manage None Enquiry queue
Portal uploads Opens /admin/portal/uploads portal.upload.review None Upload review list

Rules and messages

  • "Choose customer or agent." (422) when the type is missing or unknown.
  • "Partner profile was not found." (404) when the profile is not in the active company.
  • "The partner profile must be active." (409).
  • "This partner already has a portal organization of this type." (409).

Staff refusals after a button press show an error page with the HTTP status, a heading such as "The request could not be completed" or "Not allowed", the message above and a reference number. Use the browser Back button to return.

Role differences

  • Only roles with portal.organization.manage can open this page. Others get the "Not allowed" page ("Your roles do not include this page. Ask an administrator if you need access.") on a direct URL.

Not allowed page for a user without portal permissions - The sidebar link Customer portal is shown to anyone holding any portal.organization, portal.shipment.grant, portal.content or portal.document.publish permission, but the page itself needs portal.organization.manage (see the gaps file). With the seeded roles this only matters for custom roles.

Portal organisation

Open it from Manage or the organization name on the list. Route: /admin/portal/organizations/{id}.

Portal organization page with numbered sections

Numbered callouts: (1) page header with type tag, status badge and header buttons; (2) the section menu "Sections of this organization" (أقسام هذه المؤسسة); (3) the Members section; (4) the Shared shipments section.

The header shows the organization name, a Customer or Agent tag and the status badge. Header buttons, each shown only with its permission: Preview what this organization sees (معاينة ما تراه هذه المؤسسة), Review customer uploads, and Suspend organization or Reinstate organization. A suspended organization shows the banner "Every membership of this organization is blocked until it is reinstated."

The section menu jumps to: Members, Verified contacts, Shared shipments, Milestones, Documents, Document requests, Notifications. Counts show the number of members and active shares.

After Create invitation link (or approving an "add" member request) the page shows a yellow notice: "Invitation link (shown once)." with "Send it to {email} by a channel you trust." and "It expires in 72 hours and works once. The link is not stored in readable form and cannot be shown again." Use Copy to copy the link. If a mail transport and the encryption key are configured, an invitation email is also queued and listed under Account emails. Portal emails are English only (D18).

Portal policy

Panel Portal policy (سياسة البوابة), shown with portal.organization.manage. Form /admin/portal/organizations/{id}/policy.

Portal policy switches

Field Required What to enter / rules
uploads_enabled — "Members may upload documents to any shared shipment (otherwise only against a request or an enquiry)" No On: collaborators and administrators can upload to any shared shipment. Off: only against an open document request or an open enquiry.
billing_access — "Billing readers may see posted invoices" No Customer organizations only (hidden for agents). Turns on the billing extension; each member still needs the Billing switch. Off by default (Q25).

Button Save policy. Audit portal.organization.policy with before and after values.

Messages: "Portal organization was not found." (404); "Billing access is available to customer organizations only. Agent settlement is a separate extension." (409).

Members

Section Members (الأعضاء). Columns: Name, Email, Role (Viewer, Collaborator, Administrator), Status (Active, Suspended, Revoked), Access, actions.

Access column (with portal.organization.manage), form /admin/portal/memberships/{id}/access:

Field Required What to enter / rules
billing_reader — "Billing" No Customer organizations only. Lets this member open Invoices in the portal. Needs organization billing access first.
export_allowed — "Export" No Lets this member request CSV exports in the portal.

Button Save. Message: "Turn on billing access for the organization first." (409); "Membership was not found." (404). Audit portal.membership.access.

Actions column (with portal.member.suspend), form /admin/portal/memberships/{id}/status:

Button What it does Confirmation Result
Suspend Status Suspended. "Suspend this member? They cannot sign in until reinstated." The person cannot use this organization. If they have no other active membership, all their sessions are signed out. Queued notifications for them are cancelled.
Revoke Status Revoked. "Revoke this membership? The person loses portal access for this organization." As Suspend.
Reinstate Status Active (shown for suspended and revoked rows). None The only way back; an invitation never reactivates a membership.

Messages: "This membership cannot change to that status." (409); "Unknown membership status." (422).

Member requests from the organization

Shown only when the organization administrator has sent requests from the portal Members page. "The organization administrator asked for these changes. Nothing changes until you approve. Approving an addition sends a new invitation link; the link is shown once." Columns: Requested, By, Change ("Add {email} as {role}" or "Remove {name} ({email})"), Reason, Status (Pending, Approved, Rejected), Decision.

Decision form /admin/portal/requests/{id}/decision: field reason — "Reason (needed to refuse)" (optional to approve, required to refuse, up to 500 characters), buttons Approve and Refuse (confirmation "Refuse this request?").

  • Approving an add runs the normal invitation (all invitation rules apply, including the verified-contact rule) and shows the invitation link once. Approving a remove revokes the membership.
  • Deciding an add needs portal.member.invite; a remove needs portal.member.suspend. Without it the row shows "Needs another permission". A suspended organization shows "Reinstate the organization first".
  • Messages: "Choose approve or reject." (422), "Give a reason for refusing the request." (422), "The reason is too long." (422), "Request was not found." (404), "You do not have permission for this action." (403), "This request has already been decided." (409). Spec §33.2, P07.

Verified contacts

Section Verified contacts (جهات الاتصال الموثقة). "An invitation can only go to a contact of this organization's partner that staff have verified, and to that contact's email. Record how you confirmed the person." It lists the contacts of the organization's partner only. Columns: Contact, Email, Verification (Verified with method and date, Not verified, or Revoked).

Verified contacts and the invitation form

Verify form /admin/portal/organizations/{id}/contacts/verify (with portal.member.invite, contacts with an email):

Field Required What to enter / rules
method — "How it was confirmed" Yes Phone call, In person, Signed form or Existing relationship.
note — "Note (optional)" No Up to 255 characters.

Button Verify. Audit portal.contact.verified. Messages: "Choose how the contact was verified." (422), "Keep the note under 255 characters." (422), "That contact does not belong to this organization's partner." (404), "Only an active contact with an email address can be verified." (409), "This contact is already verified for the organization." (409).

Withdraw verification (/admin/portal/contact-verifications/{id}/revoke), confirmation "Withdraw this verification? New invitations to this contact are refused." It also revokes pending invitations to that email and cancels their queued emails. Message: "Verification was not found." (404).

Invite a person (دعوة شخص) footer, form /admin/portal/organizations/{id}/invitations, shown with portal.member.invite while the organization is active:

Field Required What to enter / rules
Email Yes Up to 255 characters. Must be the email of a verified contact of this organization.
Role No Viewer (read only), Collaborator, Administrator. Defaults to Viewer. Accepting the invitation does not turn on a second sign-in code.

Button Create invitation link. A newer invitation to the same email revokes the older pending one (resend). Audit portal.invitation.issued (the token is recorded as redacted). Messages: "Enter a valid email address." (422), "Choose a portal role." (422), "The organization is suspended." (409), "This email cannot receive a portal invitation." (409, the email belongs to a staff account), "This person already has access to the organization." (409), "This membership is suspended or revoked. Reinstate it instead of inviting again." (409), "Verify this person as a contact of the organization's partner before inviting them." (409).

Recent invitations

Shown when invitations exist (last 25). Columns: Email, Role, Status (Pending, Accepted, Revoked), Expires. An invitation whose expiry has passed keeps the Pending badge; compare the Expires time (see gaps).

Shared shipments

Section Shared shipments (الشحنات المشتركة). Columns: Shipment, Status (Active or Revoked), Reason, Granted, Scope, actions. Scope shows the field groups as tags (or "Timeline and documents only" when none), "Until" with the expiry and an Expired badge once it has passed, and "Lots: …" when the share is narrowed to lots.

Shared shipments with scope and revoke

Share a shipment (مشاركة شحنة) footer, form /admin/portal/organizations/{id}/grants, shown with portal.shipment.grant. The Shipment list holds up to 100 company shipments not already shared with this organization.

Share a shipment form

Field Required What to enter / rules
Shipment Yes Shipment reference.
Reason (required, shown in audit) Yes 3 to 255 characters.
Valid through (end of day UTC, optional) No A future date. The share ends at 23:59:59 UTC that day. Empty means no end.
fields[] — "Shared field groups" No Route, Cargo (ticked by default), Carrier references (not ticked). Nothing ticked shares the timeline and documents only.

Button Share shipment. A revoked share of the same shipment is reactivated, not duplicated. Audit portal.shipment.granted; members get the portal notice "A shipment was shared with you".

Change scope (تغيير النطاق) on an active row, form /admin/portal/grants/{id}/scope:

Field Required What to enter / rules
fields[] No The field groups as above.
lot_ids[] — "Lot #{id} ({pieces} pcs)" No Lots of this shipment. "No lot ticked shares every lot."
valid_until — "Valid through (end of day UTC, empty for no end)" No A future date or empty.

Button Save scope. Audit portal.shipment.scope. Messages: "Active grant was not found." (404), "A chosen lot does not belong to this shipment." (422).

Revoke share (/admin/portal/grants/{id}/revoke), confirmation "Revoke this share? The organization loses access to the shipment and its documents are withdrawn at once." Audit portal.shipment.revoked with the number of withdrawn publications and documents.

Messages for shares: "Give a reason, 3 to 255 characters." (422), "Give the expiry as a date (YYYY-MM-DD)." (422), "The expiry must be a future date." (422), "The organization is suspended." (409), "Shipment was not found." (404), "This shipment is already shared with the organization." (409), "Grant was not found." (404), "This grant is not active." (409).

Milestones of shared shipments

Section Milestones of shared shipments (مراحل الشحنات المشتركة). "Only recorded milestones can be published. Publishing shows the customer label from the template. Superseded milestones cannot be published." Columns: Shipment, Step, Milestone (code), Recorded, Record state (Recorded or Superseded), Customer (Published, Unpublished, or "Not published"), actions.

Milestones with publish and unpublish

Button Route Confirmation Result
Publish /admin/portal/organizations/{id}/publish (sends event_id) "Publish this milestone to the organization?" Customer sees it on the shipment timeline with the customer label from the milestone template. Audit portal.publication.published; members get the portal notice "Status update: {detail}".
Unpublish /admin/portal/publications/{id}/revoke "Unpublish this milestone? The organization no longer sees it." Removed from the timeline; queued notices for it are cancelled. Audit portal.publication.revoked.

Buttons appear only for recorded milestones, with portal.content.publish, while the organization is active. Messages: "Milestone was not found." (404), "Only a recorded milestone can be published." (409), "Share the shipment with this organization first." (409), "This milestone is already published to the organization." (409), "Publication was not found." (404), "This milestone is not published." (409).

Documents on shared shipments

Section Documents on shared shipments (المستندات على الشحنات المشتركة). "A stored file reaches the organization only when you publish it here, for one shared shipment. Quarantined files cannot be published. Revoking a share also withdraws its documents at once." Columns: Shipment, File, Size, File state, Customer label, Publication (Published, Withdrawn, or "Not published"), actions.

Documents and the Publish a file form

Publish a file (نشر ملف) footer, form /admin/portal/organizations/{id}/documents, shown with portal.content.publish when the organization is active, has an active share and the company has available files. Confirmation: "Publish this file to the organization?"

Field Required What to enter / rules
Shared shipment Yes One of the organization's active shares.
Stored file Yes "#id name". The list holds the 100 newest available files of the whole company, not only this shipment's. Check the file before publishing.
Customer label (optional) No 3 to 128 characters. Defaults to the file name.
Category (portal-eligible only) No "No category" or a document category marked portal eligible.
Marker No Final, Draft or Copy. Draft and Copy show as a tag in the portal (P15).
Issuer (optional) No Up to 200 characters. Shown as "issued by …".
Replaces published document (optional) No "Nothing" or a published document on this shipment. The old version becomes superseded.
release_sensitive — "Release-sensitive (held while the job has a document release hold)" No While the job has a document release hold, the portal shows "On hold. Please contact customer service." and refuses the download.
superseded_visible — "Keep the replaced version visible (marked as replaced)" No Keeps the old version listed, marked "Replaced".

Button Publish. Audit portal.document.published (and portal.document.superseded for a replacement). Messages (from the service): "Give the document a label, 3 to 128 characters.", "Mark the document as final, draft or copy.", "The issuer must be at most 200 characters.", "Portal organization was not found.", "The organization is suspended.", "File was not found.", "Only an available file can be published.", "Shipment was not found.", "Share the shipment with this organization first.", "The document being replaced was not found on this shipment.", "Only a published document can be replaced by a new version.", "A new version needs a different file.", "Choose a known document category.", "This document category is not eligible for the portal.", "This document is already published to the organization."

Withdraw (/admin/portal/document-publications/{id}/revoke), confirmation "Withdraw this document? The organization can no longer download it." Audit portal.document.revoked. Messages: "Document publication was not found." (404), "This document is not published." (409).

Document requests

Section Document requests (طلبات المستندات). "Ask the organization for a document on a shared shipment. Members can then upload against the request; every upload is reviewed before it is accepted." Columns: Shipment, Document, Instructions, Status (Open, Fulfilled, Cancelled), Requested. An open request also marks the shipment "Needs your attention" in the portal.

Document requests

Request a document (طلب مستند) footer, form /admin/portal/organizations/{id}/document-requests:

Field Required What to enter / rules
Shared shipment Yes One of the active shares.
Document Yes One of the upload document types: Customer instructions, Commercial invoice, Packing list, Certificate of origin, Insurance certificate, Proof of delivery.
Instructions for the customer Yes (service) 5 to 500 characters. The field is not marked required on screen.

Button Request. Audit portal.document_request.opened. Cancel on an open row (/admin/portal/document-requests/{id}/cancel), confirmation "Cancel this document request?". Approving an upload made against the request marks it Fulfilled.

Messages: "Choose one of the document types customers can upload." (422), "Instructions must be 5 to 500 characters." (422), "Share the shipment with this organization first." (409), "Document request was not found." (404), "This request is already closed." (409).

Notifications and Account emails

Notifications (with portal.organization.manage): counts by Channel (In app, Email) and Status. "Queued email waits for the scheduled task. Provider accepted means the mail provider accepted the message, not that it was read. Cancelled rows were stopped by a revocation, a suspension, or a withdrawal."

Account emails: invitation and security emails with Email, Kind, Status, Attempts, Queued.

Notification counts

Actions

Button/link What it does Permission Confirmation Result
Suspend organization /admin/portal/organizations/{id}/status with Suspended portal.organization.manage "Suspend this organization? Every membership is blocked until it is reinstated." Members cannot sign in to it; queued notifications cancelled. Audit portal.organization.suspended.
Reinstate organization Same route with Active portal.organization.manage "Reinstate this organization? Its active members can sign in again." Audit portal.organization.active.
Preview what this organization sees Opens /admin/portal/organizations/{id}/preview portal.audience.preview None Audience preview
Review customer uploads Opens /admin/portal/uploads portal.upload.review None Upload review
Save policy, Save, Suspend, Revoke, Reinstate, Approve, Refuse, Verify, Withdraw verification, Create invitation link, Share shipment, Save scope, Revoke share, Publish, Unpublish, Withdraw, Request, Cancel See the sections above See the sections above See the sections above Page reloads on the organization

Staging only: suspending an organization, or suspending or revoking a member, blocks real people from the portal at once and cancels their queued notices. In a shared test environment use a test organization or member.

Status messages: "Unknown organization status." (422), "The organization already has this status." (409).

Rules and messages

  • Every action is checked again on the server and audited. The page is opened with portal.organization.manage; without it the page is refused with "You do not have permission for this action." (403).
  • Forms stay hidden while the organization is suspended (invite, share, publish, request), and decisions show "Reinstate the organization first".
  • After a refusal the error page shows the message; go back and correct the entry.

Role differences

  • Without portal.member.invite: no Verify, Withdraw verification or invitation form.
  • Without portal.member.suspend: no Suspend, Revoke or Reinstate buttons.
  • Without portal.shipment.grant: no Share a shipment, Change scope or Revoke share.
  • Without portal.content.publish: no Publish, Unpublish, Withdraw or Publish a file.
  • Without portal.audience.preview or portal.upload.review: the matching header button is hidden.

In Arabic the page runs right to left; emails, references and dates keep their left-to-right order.

Portal organization page in Arabic - The Request a document form is shown to holders of portal.organization.manage, and Cancel to everyone who can open the page, but the service requires portal.upload.review (see the gaps file).

Portal preview

Open it with Preview what this organization sees. Route: /admin/portal/organizations/{id}/preview; add ?shipment={id} (button Preview shipment) for one shipment. Title "Audience preview: {name}" (معاينة الجمهور: {name}).

Audience preview of an organization

The blue notice reads "Preview only." and "This shows what the members of this organization see in the customer portal. You are not signed in as a member. Nothing here changes an account, a grant, or a publication, and no download is offered. Each preview is recorded in the audit log by count and ID only."

  • When the organization is suspended: "This organization is {status}. Its members cannot sign in, so they see no shipments, documents or notices."
  • KPI cards: Granted shipments and With a published update.
  • Shipments table: Reference, Mode, Direction, Latest published status ("No published update" when none), Latest update, Preview shipment.
  • With a shipment chosen: Published timeline and Published documents with "Download is not offered in preview". A shipment that is not shared shows "This shipment is not shared with this organization. Its members see the same not-found page as for a shipment that does not exist."

Preview of one shipment

Messages: "You do not have permission for this action." (403), "Portal organization was not found." (404). Each preview writes audit portal.audience.previewed. Button Back to {name} returns to the organization.

Role differences: needs portal.audience.preview.

Portal enquiries

Open it from Administration > Portal enquiries (استفسارات البوابة) or the Portal enquiries button. Route: /admin/portal/enquiries. The fact pack also lists it as POST /admin/portal/enquiries; the screen is a GET page.

Portal enquiry queue

"Customer and agent questions on shared shipments. Urgent first, then by response due time. Closed enquiries are hidden unless you pick Closed." Up to 200 rows.

Fields

Filter form GET /admin/portal/enquiries:

Field Required What to enter / rules
status — "Status" No "Open (not closed)" (default) or one status: Open, Assigned, Awaiting our reply, Awaiting your reply, Resolved, Closed.

Button Filter.

Columns: Subject, Priority (Urgent badge or Normal), Organization, Shipment, Category, Status (with a New badge when the customer wrote something unread), Due, Owner ("Unassigned"), Open.

Enquiry statuses as the code sets them:

Status Set when
Open The customer creates the enquiry. Clearing the owner of an Assigned enquiry returns it to Open.
Assigned An owner is set on an Open enquiry.
Awaiting our reply The customer replies.
Awaiting your reply Staff post a reply to the customer.
Resolved Staff set it, or a change request is decided.
Closed Staff set it, or the customer withdraws (shown as Withdrawn in the portal).

Staff can set any status with Set status. Due times: normal priority 24 hours, urgent 4 hours, or 8 and 2 working hours on the branch business calendar when one exists.

Role differences: needs portal.enquiry.manage; otherwise the sidebar link is hidden and the page shows "Not allowed".

On a phone (390 px) the queue keeps every column; scroll the table sideways inside its panel.

Portal enquiry queue on a phone

Portal enquiry

Open it with Open or the subject. Route: /admin/portal/enquiries/{id} (the fact pack lists POST /admin/portal/enquiries/{id}; it is a GET page).

Picture not taken (14-portal-enquiry). The demo has no portal enquiry in the staff queue. The reason is in the screenshot index.

Numbered callouts: (1) summary (Organization, Shipment, Category, Priority, Due, Owner); (2) Reply form; (3) Owner; (4) Status.

The summary shows Organization, Shipment, Category, Priority ("Urgent" or "Normal priority"), Due and Owner. The header has Enquiry queue.

Change request: {label} (طلب تغيير: {label}) panel, shown for enquiries of category Change request: "Deciding records your answer and tells the customer. It does not change the shipment, job or any posted record; apply an accepted change through the normal operations screens." It shows the customer's text and the status (Pending, Accepted, Declined, Withdrawn). Spec §33.8, P14.

Messages (الرسائل): all messages, each marked "Internal note, not visible to the customer" or "Visible to the customer", with "Team" or "Customer or agent".

Reply (رد) panel. A closed enquiry shows "This enquiry is closed. Set a status to reopen it before replying."

Handling (المعالجة) panel: Owner, Status, Priority.

Attachments from the customer (مرفقات من العميل): files uploaded on this enquiry with their review status and a Review queue button.

Picture not taken (14-portal-enquiry-reply-form). The demo has no portal enquiry in the staff queue. The reason is in the screenshot index.

Fields

Change request form /admin/portal/enquiries/{id}/variation (pending change requests only):

Field Required What to enter / rules
Answer to the customer (5 to 500 characters) Yes The customer sees it. The screen allows 500 characters; the service accepts up to 1000.

Reply form /admin/portal/enquiries/{id}/reply:

Field Required What to enter / rules
Message Yes 10 to 4000 characters.
audience — "Audience" Yes "Internal note (private, the safe default)" (selected) or "Reply to the customer or agent".

Handling forms /admin/portal/enquiries/{id}/assign, /admin/portal/enquiries/{id}/status, /admin/portal/enquiries/{id}/priority (route pattern /admin/portal/enquiries/{id}/(reply|assign|status|priority)):

Field Required What to enter / rules
owner_user_id — "Owner" No "Unassigned" or an active staff member of the company.
status — "Status" Yes One of the six statuses.
priority — "Priority" Yes Normal or Urgent. Changing it recalculates the due time from now.

Actions

Button What it does Permission Confirmation Result
Accept Decides the change request as accepted. portal.enquiry.manage "Accept this change request? The customer is told; apply the change through the operations screens." Variation Accepted; a customer-visible message "Change request accepted: …" is posted; enquiry Resolved; the requester gets the portal notice "Your change request was answered". No shipment, job or posted record changes. Audit portal.variation.accepted.
Decline As Accept, declined. portal.enquiry.manage "Decline this change request? The customer is told." Variation Declined; enquiry Resolved. Audit portal.variation.declined.
Post Adds the message. portal.enquiry.manage None Internal note: status unchanged, audit portal.enquiry.note_added. Reply to the customer: status Awaiting your reply, the portal notice "New reply: {detail}", audit portal.enquiry.replied_to_customer.
Assign Sets the owner. portal.enquiry.manage None Open becomes Assigned; clearing the owner of an Assigned enquiry makes it Open. Audit portal.enquiry.assigned.
Set status Sets the status. portal.enquiry.manage None Closing stamps the close time. Audit portal.enquiry.status.
Set priority Sets the priority. portal.enquiry.manage None New due time. Audit portal.enquiry.priority.

Rules and messages

  • "Choose who can read the message." (422), "Write a message, 10 to 4000 characters." (422), "Reopen the enquiry before replying." (409).
  • "Choose an active team member." (422), "Choose a status." (422), "The enquiry already has this status." (409), "Choose a priority." (422), "The enquiry already has this priority." (409).
  • "Give the customer a reason, 5 to 1000 characters." (422), "This enquiry has no change request." (404), "This change request has already been decided or withdrawn." (409).
  • "The enquiry was not found." (404) for an enquiry of another company.

Role differences

Needs portal.enquiry.manage for the page and every action.

Portal uploads

Open it from Administration > Portal uploads (مرفوعات البوابة), Portal uploads or Review customer uploads. Route: /admin/portal/uploads, with ?status=pending (default), approved or rejected. Title "Customer uploads" (مرفوعات العملاء).

Customer uploads waiting for review

"Files sent through the portal. Each one passed the automatic type and content checks and is held in quarantine until you approve it. Download a file to inspect it; it is never opened in the browser." The tabs "Upload review lists" (قوائم مراجعة المرفوعات) are Waiting for review (بانتظار المراجعة), Approved (معتمد) and Rejected (مرفوض).

Columns: File (name, type, size, Download to inspect), Received (time and uploader), Organization, Shipment, Type (category code), Checks ("basic checks passed" with the note "Size, type and embedded script checks only. No antivirus scan on this host."), Linked to ("Request #{id}", "Enquiry #{id}"), actions.

Approved uploads tab

Fields

Field Required What to enter / rules
reason — "Reason shown to the customer" (Reject form) Yes 5 to 500 characters. The uploader sees it under the file.

Actions

Button/link What it does Permission Confirmation Result
Download to inspect /admin/portal/uploads/{id}/file, served as an attachment portal.upload.review None File downloads; audit portal.upload.reviewed_file.
Approve /admin/portal/uploads/{id}/approve portal.upload.review "Approve this file? It is released from quarantine." Review Approved; file becomes available and linked to the shipment; a linked document request becomes Fulfilled. The file is not published to the portal until staff publish it. Audit portal.upload.approved.
Reject /admin/portal/uploads/{id}/reject portal.upload.review "Reject this file? The reason is shown to the customer." Review Rejected, file Rejected, reason shown to the uploader as "Not accepted". Audit portal.upload.rejected.

Route pattern: /admin/portal/uploads/{id}/(approve|reject).

Rules and messages

  • "Give a reason the uploader will see, 5 to 500 characters." (422).
  • "Upload was not found." (404); "This upload has already been reviewed." (409).
  • "You do not have permission for this action." (403).

Role differences

Needs portal.upload.review.

Procedures

Set up a customer for the portal

  1. Open Administration > Customer portal.
  2. In Create an organization, choose the Partner profile, choose Type Customer, and press Create organization.
  3. On the organization page, in Verified contacts, pick How it was confirmed for the contact, add a note if useful, and press Verify.
  4. Under Invite a person, enter the contact's Email, choose Role, press Create invitation link.
  5. Press Copy in the "Invitation link (shown once)." notice and send the link through a channel you trust. It works once and expires in 72 hours.
  6. Optional: in Portal policy set the switches and press Save policy.

Share a shipment and publish its updates

  1. On the organization page, in Share a shipment, choose the Shipment, enter Reason (required, shown in audit), optionally Valid through (end of day UTC, optional), choose the Shared field groups, and press Share shipment.
  2. In Milestones of shared shipments, press Publish on each recorded milestone the customer should see and confirm.
  3. In Publish a file, choose Shared shipment and Stored file, fill Customer label (optional), Category (portal-eligible only), Marker and Issuer (optional), and press Publish, then confirm.
  4. Press Preview what this organization sees, then Preview shipment, and check the timeline and documents.

Stop sharing a shipment

Staging only: revoking a share also withdraws every published milestone and document of that shipment for the organization. Republishing is manual.

  1. In Shared shipments, press Revoke share on the row and confirm.
  2. Check that the milestones of that shipment show Unpublished and its documents Withdrawn after reload.

Answer a portal enquiry

  1. Open Administration > Portal enquiries and press Open on the row.
  2. Under Handling, choose the Owner and press Assign.
  3. In Reply, write the Message, choose Reply to the customer or agent (or keep the internal note) and press Post.
  4. When the matter is done, choose Status Resolved or Closed and press Set status.

Decide a change request

  1. Open the enquiry. In Change request: {label}, enter Answer to the customer (5 to 500 characters).
  2. Press Accept or Decline and confirm.
  3. If accepted, make the change yourself in the operations screens; the decision changes nothing on the shipment.

Review an upload

  1. Open Administration > Portal uploads.
  2. Press Download to inspect and check the file on your computer.
  3. Press Approve and confirm, or enter Reason shown to the customer and press Reject and confirm.
  4. To show an approved file to the customer, publish it from the organization page (Publish a file).

Decide a member request

  1. On the organization page, find Member requests from the organization.
  2. For an add, make sure the email is a verified contact first (otherwise approval is refused).
  3. Press Approve, or enter Reason (needed to refuse) and press Refuse and confirm.
  4. For an approved add, copy the invitation link from the notice.

Troubleshooting

Symptom Cause What to do
"Verify this person as a contact of the organization's partner before inviting them." The email is not a verified contact of this partner. Add the contact on the partner record, then Verify it here.
"This membership is suspended or revoked. Reinstate it instead of inviting again." The person had access before. Press Reinstate in Members.
"This email cannot receive a portal invitation." The email belongs to a staff account. Use another email; staff accounts cannot be portal members.
Customer says a shipment shows "Not found" No active share, the share expired, or the organization or membership is suspended. Check Shared shipments (Expired badge) and Members status.
Customer sees the shipment but no updates Milestones are not published. Publish them in Milestones of shared shipments.
A file is not in Stored file It is quarantined or not available. Approve the upload first, or check the file state.
"Share the shipment with this organization first." No active share for the shipment. Share it, then publish.
Customer gets "On hold. Please contact customer service." The document is release-sensitive and the job has a document release hold. Lift the hold in operations, or publish a version that is not release-sensitive.
"Turn on billing access for the organization first." Billing switch on a member without organization billing access. Turn on Billing readers may see posted invoices and Save policy first.
Invitation still shows Pending after 72 hours The badge does not change at expiry. Compare Expires; create a new invitation link.
"Form expired" page The page was open too long (stale form token, 419). Reload the page and repeat the action.

15. Customer and agent portal

The portal is the external view of the system for customers and agents. A portal user signs in with an external account and sees only what staff have explicitly shared with their organization: the shipments that have an active share, the milestones and documents that staff published, and the field groups (route, cargo, carrier references) included in the share. Everything else, including internal notes, costs, unpublished milestones and other organizations' data, is never shown (Spec §33.3, §33.6). Chapter 14 explains how staff set this up.

The portal has its own layout and its own keyed text catalogue (English and Arabic). Portal emails are sent in English only (D18).

Who uses it: external users with a membership in a portal organization. The demo users for this chapter are {PORTAL_ADMIN} (organization administrator) and {PORTAL_VIEWER} (viewer). Spec §33.1-§33.12. UML 21, 22, 39, 40, 46.

External role matrix (Spec §33.4)

What a member can do is decided by the membership role (Viewer, Collaborator, Administrator) set by staff, plus the per-member switches Billing and Export. The seeded role templates "Customer viewer", "Customer collaborator", "Customer organization administrator", "Agent viewer" and "Agent collaborator" carry portal.shipment.list and portal.shipment.view; the collaborator and administrator templates add portal.enquiry.create, portal.upload.create and (customer only) portal.document.download. The portal pages check the membership role, as below.

Capability Viewer Collaborator Administrator
Overview, Shipments, shipment tabs (as shared) Yes Yes Yes
Download a published document No ("View only for your role") Yes Yes
Upload a file No Yes Yes
Read enquiries Yes Yes Yes
Open or reply to an enquiry No Yes Yes
Withdraw an enquiry No Own enquiries Any enquiry of the organization
Download own approved uploads No Yes Yes
Members page and member change requests No No Yes
Invoices Only with the Billing switch, on a customer organization with billing access Same Same
Exports Only with the Export switch (invoice exports also need Billing) Same Same
Notifications, Profile, Security Yes Yes Yes
Two-step sign-in Optional, unless the organization requires it Optional, unless required Always required

Portal layout

The top bar shows the organization name and type (Customer or Agent). The main menu has Overview (نظرة عامة), Shipments (الشحنات), Enquiries (الاستفسارات), Notifications (الإشعارات) with an unread count, Invoices (الفواتير, only with billing), Exports (التصدير, only with export) and Members (الأعضاء, only for administrators). The tools area has Switch organization (تبديل المنظمة, only with more than one membership), the user's name (opens Profile), Security (الأمان), the language link and Sign out.

  • Language: the link shows "العربية" in English and "English" in Arabic. It opens /locale?lang=ar (or en) and returns to the same page. In Arabic the page runs right to left. The choice lasts for the session; to keep it, set Language on the Profile page.
  • On a phone (390 px) the menu folds behind the Menu (القائمة) button.
  • The footer shows "Times shown in {time zone}".

Portal menu opened on a phone

Portal message page

Every refusal in the portal is shown on one message page with the heading Not available (غير متاح), the reason and the status code. A shipment, enquiry, invoice page or export that is not shared or does not exist shows Not found (غير موجود) with "That page does not exist or is not shared with you." with status 404. The same page is used for a missing ID and for an ID that exists but is not shared, so a user cannot tell them apart (P01, P06).

Picture not taken (15-portal-shipment-not-found). No ungranted shipment id was available to open. The reason is in the screenshot index.

Other headings:

  • No portal access (403): "Your sign-in has no active portal organization. Contact your Freight Forwarding contact if you expected access." Shown when the account has no active membership (suspended, revoked or organization suspended).
  • Not available for your role (403): "Your role can read enquiries but cannot open them. Ask a collaborator in your organization."
  • An external user who opens a staff address such as /admin/portal gets the portal Not found page (P06).
  • A form left open too long shows the plain "Form expired" page (419, "The form token is not valid. Reload the page and try again.").

The reason text on this page is English even when the portal is in Arabic (see the gaps file).

Sign in

Open /portal/login (or /login; both show the same sign-in page). Title "Sign in" (تسجيل الدخول).

Portal sign-in page

Fields

Field Required What to enter / rules
Email Yes The email the invitation was sent to.
Password Yes The password chosen when accepting the invitation.

Actions

Button/link What it does Permission Confirmation Result
Sign in POST /login None None External accounts open the portal Overview, or /portal/select when several organizations are available.
Forgot password? Opens the reset request None None See chapter 1.

Rules and messages

  • "The email or password is not valid." (401).
  • "Too many sign-in attempts. Try again later." (429) after 5 failed attempts for the same email and address within 15 minutes.

Staging only: testing the sign-in limit blocks that email from your address for 15 minutes; do not use a shared demo account.

Role differences

Same for all roles. There is no self-registration; access starts with an invitation (Q26).

Accept your portal invitation

Open the link from the invitation: /portal/invite?token=…. Route /portal/invite (GET and POST). Title "Accept your portal invitation" (قبول دعوة البوابة).

Picture not taken (15-portal-invite-form). An invitation link was not captured, because the token must not be written into a file. The reason is in the screenshot index.

The page says "You have been invited to the portal of {organization} as {role}. Sign in with" followed by the invited email.

Fields

Field Required What to enter / rules
Name (display_name) Yes 2 to 200 characters.
Password (password) Yes At least 12 characters with a letter and a number. If you already have a portal account for this email, enter its password instead.

Actions

Button What it does Permission Confirmation Result
Accept invitation POST /portal/invite Valid link None Creates the external account (or uses the existing one), activates the membership, signs you in and opens the Overview. The link cannot be used again.

Rules and messages

  • A wrong, used, revoked or expired (over 72 hours) link shows Invitation not valid: "This invitation link is not valid or has expired. Ask the person who invited you for a new link." (404, P05).
  • "Enter your name, 2 to 200 characters."; "Use at least 12 characters with a letter and a number."; "The password is not valid." (existing account, wrong password); "An account with this email already exists. Sign in with it and try the link again."; "Too many sign-in attempts. Try again later."
  • The invitation can only be accepted for the email it was issued to.

Invalid invitation link

Choose an organization

Route /portal/select (GET shows the list, POST opens the chosen organization). Shown after sign-in when the account has more than one active membership, and from Switch organization. Title "Choose an organization" (اختر منظمة).

Organization selection

"Your sign-in has access to more than one organization. Choose the one you want to view. You can switch later." Each row shows the organization, its type (Customer or Agent) and an Open button (membership_id).

Rules: "Organization was not found." for a membership that is not yours or not active. After switching, every page shows only the chosen organization's shares; data of the other organization is not mixed in (P08, P16).

Portal overview

Route / or /dashboard for a portal user. Menu Overview (نظرة عامة).

Portal overview with numbered areas

Numbered callouts: (1) the portal menu; (2) the stage tiles; (3) Recent updates.

  • Tiles: Active shipments, Awaiting departure, In transit, Arrived, Delivered (opens the Delivered list) and Needs your attention (an enquiry awaiting your reply or an open document request). Each tile opens the shipments list filtered to that stage.
  • Recent updates: the latest published milestones with the shipment reference and time. Empty: "No updates have been published for your shipments yet."
  • "{count} unread notifications" links to Notifications.

Stages come from the latest published milestone: Not started, Awaiting departure, In transit, Arrived, Delivered.

Role differences: same for all roles; the menu shows Members only for administrators.

Overview for a viewer

Shipments

Route /portal/shipments. Menu Shipments (الشحنات). Two lists: Active and Delivered (?view=archive, the archive of delivered shipments).

Shipments list with filters

Numbered callouts: (1) Active and Delivered; (2) the filter form; (3) the list.

Each row shows Reference, Route (only if the share includes Route), Status (the latest published milestone, or "Awaiting update") and Updated. "No update for over 24 hours" marks a stale shipment (P12). The footer shows "{total} shipments, page {page} of {pages}" with Previous and Next (25 per page).

Fields

Filter form GET /portal/shipments (labels are visible text; the controls are submitted by name):

Field Required What to enter / rules
Reference (q) No Part of the shipment reference, up to 60 characters.
Mode (mode) No Any, Ocean, Air, Road, Multimodal.
Stage (stage) No Any, Not started, Awaiting departure, In transit, Arrived, Needs your attention.
Sort by (sort) No Latest update or Reference.

Button Apply. Empty lists: "No shipments have been shared with this organization yet." or "No shared shipment matches these filters."

Delivered shipments

Role differences: same for all roles. Only shipments with an active, unexpired share appear.

Shipment

Route /portal/shipments/{id}, with tabs ?tab=overview, ?tab=cargo, ?tab=route, ?tab=documents, ?tab=enquiries. Title "Shipment" (الشحنة) with the reference.

Shipment overview tab with numbered areas

Numbered callouts: (1) header with the actions; (2) the tabs; (3) Notifications for this shipment.

The header shows Mode, Direction and the freshness line: "Last update {when}", "Updated by our team" or "Updated from the carrier feed (carrier data from {when})", or "No update for over 24 hours". Header buttons Ask about this shipment and Request a change are shown to collaborators and administrators only. "Service team: {team}" shows the branch when known.

Overview (نظرة عامة): Updates (the published timeline; "No updates have been published for this shipment yet."), Transport documents (house bill, house air waybill or road note numbers; carrier references only when shared), Notifications for this shipment. The footer reads "Dates are shown in your time zone. Internal costs, notes and unpublished milestones are not shown here."

Cargo (البضائع): Lot, Pieces, Weight, Volume, Departed, Arrived, Status (Awaiting departure, Partially departed, Departed, Partially arrived, Arrived). If the share does not include Cargo: "This information has not been shared with your organization." A share narrowed to lots shows only those lots (P11).

Cargo tab

Route (المسار): Leg {n}, Departure and Arrival with "actual", "estimated" or "not yet known", "Estimate from {source}, updated {when}", and "Carrier reference" when shared. "Estimates can change. Actual times are recorded when the movement happens."

Route tab

Documents (المستندات): Shared documents with the label, tags (New, Replaced, Draft, Copy), "Version {n}", "shared {when}", "issued by {issuer}" and Download. A viewer sees "View only for your role". A held document shows "On hold. Please contact customer service." Your uploads lists your organization's files with Waiting for review, Approved or Not accepted (with the staff reason). Send us a document (أرسل لنا مستنداً) holds the upload form.

Documents tab with the upload form

Enquiries (الاستفسارات): enquiries about this shipment ("No enquiries for this shipment yet.").

Enquiries tab

Fields

Notifications for this shipment, form /portal/shipments/{id}/subscription:

Field Required What to enter / rules
Status updates (events[]) No Notify me of published milestones.
New documents (events[]) No Notify me of published documents.
Also by email (email) No Also send an email (English only, no shipment details in the email).

Button Save. Message: "Notification settings saved for this shipment." Without saved settings the default is status updates and new documents, with email when email is on.

Send us a document, form /portal/shipments/{id}/uploads (multipart):

Field Required What to enter / rules
File (upload) Yes PDF, JPEG or PNG up to 20 MB, with a matching extension.
Document type (category) Yes Customer instructions, Commercial invoice, Packing list, Certificate of origin, Insurance certificate, Proof of delivery.
Answers request (request_id) No "Not for a request" or one of the open document requests ("Our team has asked for:"). Required when uploads are not turned on for the organization.

Button Upload. Help: "PDF, JPEG or PNG up to 20 MB. Files are checked and reviewed by our team before they are accepted." Result: "File received. Our team reviews it before it appears as approved." When uploads are closed: "Uploads for this shipment open when our team asks for a document or answers an enquiry."

Actions

Button/link What it does Permission Confirmation Result
Overview, Cargo, Route, Documents, Enquiries Switch tab (?tab=) Member None Tab content
Ask about this shipment Opens /portal/enquiries/new?shipment_id={id} Collaborator or administrator None New enquiry
Request a change Opens the new enquiry with Change request preselected Collaborator or administrator None New enquiry
Save (notifications) /portal/shipments/{id}/subscription Member None Settings saved
Download (document) /portal/documents/{id}/download Collaborator or administrator None File download (attachment). Audit portal.document.downloaded.
Download (your upload) /portal/uploads/{id}/download Collaborator or administrator, approved files only None File download
Upload /portal/shipments/{id}/uploads Collaborator or administrator None File held in quarantine for staff review

Rules and messages

  • Downloads: "The document is not available." (404, not published, withdrawn, share revoked or wrong ID); "Your role can see this document but not download it. Ask a collaborator in your organization for a copy." (403); "This document is on hold. Please contact customer service." (409, P15); uploads: "The file is not available." (404).
  • Uploads: "Choose a file to upload."; "The file is empty."; "The file is larger than 20 MB." (413); "Only PDF, JPEG and PNG files can be uploaded, with a matching file extension."; "The file could not be read."; "The file contains active content (scripts or embedded files) and was not accepted." (P13); "Your role can view shipments but not upload files." (403); "Uploads are not turned on for your organization. Answer a document request or contact customer service."; "That document request is no longer open."; "Choose one of the listed document types."; "Too many uploads. Try again later." (429, more than 30 per hour); "This request key was already used for a different file."; "The upload needs a request key of up to 128 letters, digits, dots, dashes or colons."
  • Sending the same upload twice (double click or refresh) is recognised by its request key and stored once.
  • A wrong ?tab= value shows the Overview tab.
  • Subscription: "The shipment was not found." for a shipment that is not shared.

Role differences

  • Viewer: no header buttons, "View only for your role" instead of Download, no upload form.
  • Collaborator and administrator: all of the above.

Documents tab as a viewer

Enquiries

Route /portal/enquiries. Menu Enquiries (الاستفسارات). "Questions and change requests about a shared shipment. Only your organization and our team see them." Each row shows the subject, shipment reference, category, last message time, a New reply tag and the status (Open, Assigned, Awaiting our reply, Awaiting your reply, Resolved, Closed or Withdrawn). Button New enquiry for collaborators and administrators. Empty: "No enquiries yet."

Enquiry list

Role differences: viewers see the list without New enquiry.

New enquiry

Route /portal/enquiries/new (form POST /portal/enquiries). Title "New enquiry" (استفسار جديد). "Choose a shared shipment and describe your question. Do not paste account passwords or payment details here." With nothing shared: "No shipment has been shared with your organization yet, so there is nothing to ask about."

New enquiry form

Fields

Field Required What to enter / rules
Shipment (shipment_id) Yes One of the shared shipments.
Category (category) Yes Status request, Document request, Cargo detail question, Delivery coordination, Issue or report, Change request.
What should change (change requests only) (change_type) For Change request Not a change request, Routing or schedule, Shipper, consignee or notify party, Delivery address or time, Cargo details. "A change request is reviewed by our team. Nothing on the shipment changes until they confirm it."
Subject (subject) Yes 3 to 150 characters.
Message (body) Yes 10 to 4000 characters.

Button Send enquiry. The enquiry starts as Open, normal priority; staff see it in their queue.

Rules and messages

"Choose a category."; "Choose what you want to change."; "Give a subject, 3 to 150 characters."; "Write a message, 10 to 4000 characters."; "The shipment was not found." (not shared); "Your role can read enquiries but cannot write to them. Ask a collaborator in your organization." (403).

Role differences

A viewer who opens the address gets Not available for your role.

New enquiry refused for a viewer

Enquiry

Route /portal/enquiries/{id}. Title "Enquiry" (استفسار) with the subject.

Picture not taken (15-portal-enquiry). The demo has no portal enquiry. The reason is in the screenshot index.

Shows the status, the shipment, a Change request: {type} panel (Under review, Accepted, Declined, Withdrawn, with the note "An accepted change is applied by our operations team; the shipment shows it once it is recorded."), the Conversation (only messages staff sent to you, never internal notes, P10), Attachments and the reply form.

Fields

Reply, form /portal/enquiries/{id}/messages:

Field Required What to enter / rules
Reply (body) Yes 10 to 4000 characters.

Attach a file, form /portal/shipments/{id}/uploads with the enquiry link:

Field Required What to enter / rules
Attach a file (upload) Yes Same rules as shipment uploads.

Withdraw enquiry, form /portal/enquiries/{id}/withdraw:

Field Required What to enter / rules
I want to withdraw this enquiry Yes Tick to confirm.

Actions

Button What it does Permission Confirmation Result
Send reply Adds your message Collaborator or administrator None Status Awaiting our reply; staff see a New badge.
Upload Attaches a file Collaborator or administrator None File waits for staff review.
Withdraw enquiry Closes the enquiry as withdrawn; a pending change request becomes Withdrawn The person who asked, or an administrator "Withdraw this enquiry?" Status Withdrawn

Rules and messages

"This enquiry is closed. Open a new enquiry for a new question."; "Only the person who asked or an organization administrator can withdraw this enquiry."; "This enquiry is already closed."; "Your role can read enquiries but cannot write to them. Ask a collaborator in your organization." A viewer sees "Your role can read this enquiry but cannot reply." Another organization's enquiry shows Not found.

Role differences

Viewers read only. Collaborators withdraw only their own enquiries; administrators any of the organization's.

Notifications

Route /portal/notifications. Menu Notifications (الإشعارات). "Updates for your organization. A new notice can take up to the scheduled task interval to appear. Email messages contain no shipment or document details." Notices: "A shipment was shared with you", "Status update: {detail}", "New document: {detail}", "New reply: {detail}", "Your change request was answered"; unread ones show New. Empty: "No notifications yet."

Notifications

Fields

Field Required What to enter / rules
Send me an email when there is an update for my organization (email) No "Notices in this list are always shown. This setting only controls email."

Actions

Button What it does Permission Confirmation Result
Mark as read /portal/notifications/read for one notice Member None Notice read; badge count falls
Mark all as read /portal/notifications/read for all Member None All read
Save (email) /portal/notifications/preferences Member None Email preference saved

Shipment notification settings lists shipments with their own settings ("Off", "email on", "email off"), or "All shipments use the default: status updates and new documents, with email when email is on."

Rules: emails are queued by the scheduled task and are English only (D18). A revoked share or a suspension cancels queued notices. The same event is never sent twice; a later revision says "Updated" (P17).

Invoices

Route /portal/billing. Menu Invoices (الفواتير). "Posted invoices for your organization with the amount still open. Contact customer service about payment." Columns: Invoice, Date, Total, Open, Status (Open or Paid). Empty: "No posted invoices."

Invoices address when billing is not shared: Not found

Rules: financial data is hidden by default (Q25, P18). The menu item and the page exist only for a member with the Billing switch, in a customer organization whose policy allows billing. Anyone else who types the address gets Not found. Only posted invoices of the organization's own partner are listed; drafts, costs and margins never appear.

Exports

Route /portal/exports. Menu Exports (التصدير). "CSV files of what you can see in the portal. Files are prepared by the scheduled task and kept for 7 days."

Exports address when export is not shared: Not found

Fields

Field Required What to enter / rules
Export (type) Yes Shipments, or Invoices (only with billing).

Actions

Button What it does Permission Confirmation Result
Request export POST /portal/exports Export switch None Row "Being prepared"; the scheduled task builds it (Developer-assisted to run the task on demand).
Download /portal/exports/{id}/download Your own ready export None CSV download

Statuses: Being prepared, Ready ("{n} rows"), Could not be prepared, Expired, "Not prepared: access changed" (access was removed before the file was built). Empty: "No exports yet."

Messages: "Choose shipments or invoices."; "Exports are not enabled for your account. Ask your organization administrator."; "Billing is not available for your account."; "You already have exports being prepared. Wait for them to finish." (429, three at a time); "The export is not available." (404). Without the Export switch the page shows Not found.

Members

Route /portal/members. Menu Members (الأعضاء), administrators only. "People who can sign in for your organization, with their role and status. Only the organization administrator sees this list." Columns: Name (with "You"), Email, Role (Viewer, Collaborator, Administrator), Status (Active, Suspended, Revoked, Invited), Joined.

Members with the change request form

The administrator cannot add or remove people directly. Ask for a change (طلب تغيير) sends a request to staff: "Staff approve every change. Nothing changes for your organization until they do, and a request can be refused." The Requested list shows each request as "Add {who} as {role}" or "Remove {who}" with Waiting for staff, Approved or Not approved.

Fields

Form /portal/members/requests:

Field Required What to enter / rules
Change (action) Yes Add a colleague or Remove a colleague.
Colleague email (to add) (email) For add A valid email.
Role (to add) (role) For add Viewer, Collaborator, Administrator.
Member (to remove) (membership_id) For remove "Choose a member".
Reason for staff (reason) Yes 5 to 500 characters.

Button Send request. Audit portal.member_request.created. Staff decide it on the organization page (chapter 14); an approved add sends an invitation link to the colleague.

Rules and messages

"Only the organization administrator can ask for a change to the member list."; "Choose add or remove."; "Give a reason of 5 to 500 characters."; "Choose a role."; "Enter a valid email address."; "This email cannot be used for a portal invitation."; "That person is already an active member."; "That member was not found."; "Ask staff to remove your own access."; "That access has already been removed."; "A request for this change is already waiting for staff."

Role differences

Viewers and collaborators do not see the menu item; the address gives Not available with "Only the organization administrator can see the member list." (403, recorded in the audit as portal.members.view_refused).

Members refused for a viewer

Profile

Route /portal/profile. Opened from your name in the top bar. Title "Profile" (الملف الشخصي).

Profile

Fields

Field Required What to enter / rules
Name (display_name) Yes 2 to 120 characters.
Email Read only "Your sign-in email is changed by our team on request."
Language (language) Yes English or العربية. Saved for future sign-ins.
Time zone (timezone) Yes "Dates and times in the portal are shown in this time zone."

Button Save. Result: "Profile saved." Messages: "Your name must be 2 to 120 characters."; "Choose English or Arabic."; "Choose a time zone from the list."

Security

Route /portal/security. Menu Security (الأمان).

Security page

Password (كلمة المرور), form /portal/security/password:

Field Required What to enter / rules
Current password (current_password) Yes Your current password.
New password (new_password) Yes At least 12 characters with a letter and a number.
Repeat new password (confirm_password) Yes Same as New password.

Button Change password. Result: "Password changed. Your other devices were signed out."

Signed-in devices (الأجهزة المسجّل دخولها): each device with "This device" or the browser, and "Signed in {start}, last active {seen}". "Sign out any device you do not recognise, then change your password."

Actions

Button What it does Permission Confirmation Result
Change password /portal/security/password Self None Other sessions signed out
Sign out (device) /portal/security/sessions/{session}/revoke Self "Sign out this device?" "That device was signed out."

Rules and messages

"The new passwords do not match."; "Your current password is not correct."; "Choose a password you are not using now."; "Use at least 12 characters with a letter and a number."; "That session was not found."; "Use sign out to end the session you are using."

Arabic and mobile

In Arabic every portal label comes from the Arabic catalogue, the page runs right to left, and numbers, dates and references keep their left-to-right order. On a 390 px phone the menu folds behind Menu, the shipments list shows cards instead of the table, and other tables scroll sideways inside their panel (P19).

Overview in Arabic

Shipment in Arabic

Overview on a phone

Shipments on a phone

Documents tab in Arabic on a phone

Procedures

Accept an invitation and sign in

  1. Open the link from the invitation email or message.
  2. Enter Name and a Password of at least 12 characters with a letter and a number.
  3. Press Accept invitation.
  4. If you belong to more than one organization, press Open on the one you want.

Find a shipment and download a document

  1. Open Shipments. Type part of the reference in Reference, choose Stage if needed and press Apply.
  2. Open the shipment and choose the Documents tab.
  3. Press Download next to the document. A viewer sees "View only for your role" and must ask a collaborator.

Send a document

  1. Open the shipment and choose the Documents tab.
  2. In Send us a document, choose the File, the Document type and, if our team asked for it, the request in Answers request.
  3. Press Upload. The file shows "Waiting for review" until staff approve or reject it.

Ask a question or request a change

  1. Open the shipment and press Ask about this shipment (or Request a change).
  2. Choose the Category; for a change request, choose What should change (change requests only).
  3. Enter Subject and Message and press Send enquiry.
  4. Follow replies under Enquiries; reply with Send reply.
  5. To cancel, tick I want to withdraw this enquiry, press Withdraw enquiry and confirm.

Ask staff to add or remove a colleague (administrator)

  1. Open Members.
  2. In Ask for a change, choose Change, fill the email and role (add) or the member (remove), and give a Reason for staff.
  3. Press Send request. The request shows "Waiting for staff" until staff decide.

Change password and sign out a device

  1. Open Security.
  2. Fill Current password, New password, Repeat new password; press Change password.
  3. In Signed-in devices, press Sign out next to a device you do not recognise and confirm.

Request an export

  1. Open Exports, choose Export and press Request export.
  2. Wait for the status Ready (the scheduled task prepares the file), then press Download. The file is kept for 7 days.

Troubleshooting

Symptom Cause What to do
"No portal access" after sign-in Membership or organization suspended or revoked. Contact your forwarding contact.
A shipment you expect is missing or shows "Not found" It is not shared, the share expired or was revoked. Ask staff to share it.
A shipment shows no updates No milestone has been published yet. Wait, or ask staff through an enquiry.
"View only for your role" You are a viewer. Ask a collaborator or your administrator.
"On hold. Please contact customer service." The document is held by a release hold. Contact customer service.
Upload refused Wrong type, over 20 MB, active content, or uploads not open. Send a plain PDF, JPEG or PNG under 20 MB against a request.
"Too many uploads. Try again later." More than 30 uploads in an hour. Wait and try again.
No Invoices or Exports menu The Billing or Export switch is off for you. Ask your administrator to ask staff.
Export stays "Being prepared" The scheduled task has not run yet. Wait; staff can check the scheduler.
"Invitation not valid" The link was used, replaced or is over 72 hours old. Ask for a new invitation.
Error text in English on the Arabic page Portal refusal texts are not translated yet. Use the message as shown; see the gaps list.
"Form expired" The page was open too long. Reload the page and try again.

16. Scheduler, outbox, support and recovery (developer-assisted)

This chapter explains the background work that runs without a user: the cron runner, the queue and outbox, e-mail delivery, alerts, scheduled reversals, portal exports, and the recovery tools (backup, restore, upgrade, installer, health, support evidence and audit verification). Most steps need access to the hosting panel, the file system, the database or the cron output, so everything in this chapter is Developer-assisted. Business users see the results on the screens described in Chapters 12 to 15.

Who uses it: the developer or hosting operator, together with a Platform administrator (system.backup.view, system.backup.run, system.health.view, system.cache.clear, system.integration.view, system.integration.manage, system.audit.view) and a Security administrator (system.audit.view, system.audit.export). References: Spec §22, §24, §25, §26; UML 23, 24, 25, 28, 38, 41, 47, 48, 49, 50, 51.

No live provider is configured in this environment: no integration profile is bound, mail.transport is unconfigured and backup.schedule is unconfigured. E-mail is therefore never sent here and provider calls report "not configured". Operator runbooks: docs/MAINTENANCE_RUNBOOK.md, docs/DEPLOYMENT_GUIDE.md, docs/HOST_CHECKLIST.md and docs/LIVE_PROVIDER_CHECKLIST.md. This chapter does not repeat them; it explains what the code does so testers can check it.

SCH-01 Bounded cron run (cron/run.php) and its task lock

Developer-assisted. There is no screen; the result is shown on Administration > System health, panel Scheduler (route /admin/health).

Scheduler panel on System health

One cron entry runs cron/run.php with the PHP 8.1 command-line binary every minute (DEPLOYMENT_GUIDE "Cron"). The runner:

  1. Refuses to run from the web: a web request to the file answers "The scheduler runner is not a web endpoint." (403). The cron/ folder is outside the public web root; on the local development server the address is routed to the application and redirects to /login instead.
  2. Stops with "Application is not installed." when app/config.php is missing.
  3. Takes the scheduler lease cron. If another run holds it: "Another cron run holds the scheduler lease. Nothing was done." (exit code 0). If the previous run died without releasing it and the lease expired: "The previous cron run stopped without releasing its lease; it was taken over." The lease lasts queue.cron_lease_seconds (110 in the example configuration).
  4. Works in this order, each step bounded by queue.batch_size (20) and the queue part by queue.time_budget_seconds (25):
  5. continue a backup in progress;
  6. if the maintenance gate is engaged: "Maintenance gate is engaged. Cron work skipped." and stop;
  7. queue jobs (import.commit, portal.export), see SCH-06 and Chapter 12;
  8. alert rules (SCH-05);
  9. outbox dispatch, portal e-mail, internal communications, portal account notices, purge of expired portal exports (SCH-02, SCH-06);
  10. sealing of old beneficiary rows into the bank vault;
  11. scheduled journal reversals and FX revaluation reversals, then job-completion recognition (SCH-04);
  12. booking intake mailbox polling, only when integrations.intake_mailboxes is set;
  13. audit chain verification of the next 5000 events and a signed checkpoint once a day (REC-06);
  14. a scheduled backup, when due (REC-01).
  15. Records a heartbeat in scheduled_task_runs; health turns Scheduler to warning when the heartbeat is older than queue.heartbeat_stale_seconds (180).

Fields

No fields. Settings are in the private configuration (queue.*).

Actions

Button/link What it does Permission Confirmation Result
Cron entry cron/run.php One bounded run hosting account No Lines on standard output (see Rules)
System health > Scheduler Shows heartbeat, stale lease, refused overlaps and takeovers see Chapter 13 No Panel severity

Rules and messages

  • "No scheduler heartbeat has been recorded. Cron may not be set up." (panel unknown)
  • "The heartbeat is older than 180 s, so cron is not running or is failing."
  • "A cron run stopped without releasing its lease at … UTC; the next run takes it over."
  • "Overlapping runs refused: {n}; leases taken over: {n}."
  • Queue jobs a run could not finish before its deadline are released back to pending for the next run.

Role differences

  • Only the hosting account can start cron. Users see only the health panel.

SCH-02 Outbox delivery and retry of queued email/integration messages

Developer-assisted. Results are visible in the database tables outbox_events, portal_notifications, outbound_messages and portal_account_notices, and indirectly on health and the portal.

A business change that must notify someone writes an outbox event in the same database transaction as the change, holding identifiers only. Nothing is sent from a web request. Each cron run:

  • dispatches up to 20 undispatched outbox events, each in its own transaction. A handler failure increases the event's attempt count and records the error; after 5 attempts the event is no longer picked up (it keeps dispatched_at empty; there is no separate "dead" status);
  • sends due e-mail rows. Portal notifications and internal communications go queued to provider_accepted only when the mail provider accepts them. On failure they stay queued with a retry after 300, 900, 3600 and 14400 seconds; the fifth failure sets failed. Rows whose recipient lost access become cancelled (no_longer_permitted); rows for a user who turned e-mail off become skipped (email_preference_off);
  • sends portal invitations and security notices the same way, and removes the stored secret once a notice fails for good.

With mail.transport unconfigured (this environment) every attempt fails with mail_transport_not_configured, so e-mail ends failed after five attempts and is never reported as sent. Integration calls (tax, payment links, tracking) are not sent through the outbox; they run when a user presses the button on /integrations (Chapter 13).

Fields

No fields.

Actions

Button/link What it does Permission Confirmation Result
Cron run Dispatch and delivery as above hosting account No Row statuses change

Rules and messages

Role differences

  • None; background work runs as the system.

SCH-03 Unknown external outcome reconciliation (integration attempts)

Developer-assisted with a Platform administrator. Screen: Administration > Integrations (/integrations), section Outbound attempts; the count also shows on health Tasks and integrations.

Unknown outcome with the reconcile form

When a provider times out or answers in an unexpected format, the attempt is stored with outcome unknown. It is never shown as success. A new tax submission or payment link for the same document is refused with "The last attempt has an unknown outcome. Check with the provider and record whether it was received before sending again." until someone records the provider's answer.

Cron does not reconcile unknown outcomes and does not poll tracking. Reconciliation is always a person's decision on /integrations with Record (route /integrations/attempts/{id}/reconcile). Fields and messages are in Chapter 13 (Integrations).

Fields

See Chapter 13: Provider answer (decision), external_ref, note.

Actions

Button/link What it does Permission Confirmation Result
Record Records received or not received for the unknown attempt system.integration.manage "Record this provider answer? It settles the unknown outcome." Attempt settled; audit integration.outcome_reconciled

Rules and messages

  • "Only an unknown outcome sent under an idempotency key can be reconciled." (409)
  • "A later attempt or decision exists for this submission." (409)
  • In this environment no provider is bound, so no real unknown outcome can occur; tests use a fake profile in a local or test environment only.

Role differences

  • Company administrator sees attempts but not the reconcile form.

SCH-04 Scheduled journal auto-reversals

Developer-assisted (needs cron and a journal with an auto-reverse date). Results appear on the journal pages (Chapter 10).

Each cron run posts, for every company, up to 20 pending scheduled reversals whose reverse date is today (UTC) or earlier, then the due FX revaluation reversals, then pending job-completion recognition. A reversal that is refused (for example because the period is closed) stays pending with its error recorded and is tried again on the next run. Posted reversals mark the scheduled row posted and the manual journal reversed.

Fields

No fields here; the auto-reverse date is entered on the manual journal (Chapter 10).

Actions

Button/link What it does Permission Confirmation Result
Cron run Posts due reversals system No Reversal journals posted and audited

Rules and messages

  • Reversals are dated by UTC; a reversal due "today" in Cairo may post on the next UTC day's first run.

Role differences

  • None.

SCH-05 Alerts, reminders and overdue task evaluation

Developer-assisted (needs cron and time passing). Results appear in the operations alert list (Chapter 5).

Each cron run evaluates up to 200 rows per rule and raises an alert once per window:

Rule Raised when Severity Title
task_overdue An open task is past its due time warning "Task overdue: …"
pickup_overdue A pending pickup stop on a planned or dispatched trip is past its window critical "Pickup overdue on … at …"
approval_waiting An operations approval has been pending over 24 hours warning "Approval waiting over 24 hours: …"
completed_unbilled A completed job is not fully billed warning "Completed job not fully billed: …"
customs_hold A customs case is held or under inspection critical "Customs hold or inspection on …"

New alerts are written to the outbox and become internal e-mail rows (SCH-02). With mail unconfigured, the alert is visible in the application but its e-mail is not sent.

Fields

No fields.

Actions

Button/link What it does Permission Confirmation Result
Cron run Evaluates the rules system No Alerts raised, deduplicated per window

Rules and messages

  • Thresholds are fixed in code; there is no screen to change them.

Role differences

  • None.

SCH-06 Portal notification digests and export generation

Developer-assisted. Portal users see the results on the portal (Chapter 15).

  • Notifications: each shipment event that a portal user may see becomes one in-app notification and, when e-mail is on, one e-mail row. There is no digest; each event is delivered on its own (see gaps file).
  • Exports: a portal user requests a CSV of shipments or invoices (portal API POST /exports, Chapter 18). The request is queued and a queue job portal.export builds the file on the next cron run; the export becomes ready and is kept 7 days, then the file is removed and the export becomes expired. A portal membership may have at most 3 exports being prepared at a time.

Fields

No fields.

Actions

Button/link What it does Permission Confirmation Result
Cron run Builds queued exports, removes expired ones system No Export ready or expired

Rules and messages

  • "You already have exports being prepared. Wait for them to finish." (429)
  • "The export is not available." (404) for an expired or another organization's export.

Role differences

  • Export and billing rights come from the portal membership (Chapter 14).

REC-01 Backup run, gate, external copy confirmation and retention

Screen: Administration > Backups (/admin/backups); fields and buttons are in Chapter 13.

Not allowed on Backups for the signed-in demo account

Not allowed on Backups in Arabic for the signed-in demo account

Both pictures are the Not allowed page, not a backup in progress. The steps below are what the screen does for an account that is allowed.

Staging only: a backup pauses every write for all users until it finishes.

How it works:

  1. Before anything pauses, free disk is checked; if it is too low: "There is not enough free disk space for a backup (… bytes free, about … needed). Nothing was paused." (507).
  2. The maintenance gate is engaged. Every write in the application, the machine API, booking intake and the tracking webhook answers 503 until it is released (D19 for the plain-text page).
  3. Each table and the private files are exported in chunks, encrypted with the backup key and verified. A run that does not finish in one request or cron run continues in the next ("Trigger {trigger}, step {phase}, resumed {n} times. Cron continues it if this page was closed.").
  4. The run ends verified (gate released) or failed with a reason (gate released: "The backup did not complete. Writes have resumed. See the run record for the reason.").
  5. The operator downloads the archive and the key, stores them off the server, and confirms both copies. Health warns until both are confirmed.
  6. Retention keeps the newest run of each of the last 7 days, 4 weeks and 6 months (example configuration) and always the newest verified archive. With a schedule it runs after each verified scheduled run; otherwise only with Apply retention now.

Scheduled backups run from cron when backup.schedule is hourly, every_6_hours, every_12_hours or daily. In this environment it is unconfigured, so backups run only from the page.

Fields

See Chapter 13 (Your password, Where the copy is kept and who holds it, note).

Actions

Button/link What it does Permission Confirmation Result
Run encrypted backup /admin/backups (POST) system.backup.run "Run an encrypted backup now? Writes pause until every table is exported." Run verified or failed
Release maintenance gate /admin/backups/gate/release (POST) system.backup.run "Release the maintenance gate? A run in progress ends as failed, and quarantined work is not resent." Writes resume
Confirm / Confirm key copy /admin/backups/runs/{id}/copied, /admin/backups/key/copied system.backup.run No Copies confirmed
Apply retention now /admin/backups/retention system.backup.run "Apply retention now? Archives outside the retention rule are deleted." Runs pruned

Rules and messages

  • While the gate is engaged: "Writes are paused while a backup runs. Try again in a few minutes." (503, Retry-After 60). Machine API: {"error":{"code":"maintenance","message":"Writes are paused while a backup runs."}}.
  • A run released by an administrator ends with reason gate_released_by_administrator.

Role differences

  • See Chapter 13, Backups.

REC-02 Restore into staging and prevention of duplicate outbound actions

Developer-assisted. Screen: the installer /install on a fresh copy, section Restore from an encrypted backup (route /install/restore), then Backups > Quarantined after restore on the restored copy.

Picture not taken (16-backups-quarantine). The signed-in account was refused on Backups, so the quarantine panel was not on the page. The reason is in the screenshot index.

Staging only: restore only into a new, empty database on a separate copy. Never restore over the live installation.

Preparation (MAINTENANCE_RUNBOOK "Restore"): upload the same release, put the key file in storage/private/backup-keys/ and the archive in storage/backups/, create the setup secret, save an empty database in the installer. Then choose the Archive, enter a Staging URL that differs from the source and click Restore into this database.

The restore refuses (the code is shown on the installer page, D20):

Code Meaning
restore_archive_name_invalid, restore_archive_missing The archive name is not a plain .ferpbak file name, or the file is not in storage/backups.
restore_refused_installed The target is already installed.
restore_in_progress Another restore holds the restore lock.
restore_prefix_mismatch The table prefix differs from the archive.
restore_base_url_invalid, restore_base_url_is_source The staging URL is not valid, or equals the source URL.
restore_schema_newer_than_application The archive comes from a newer release. Upload that release first.
restore_target_not_empty, restore_files_directory_not_empty The database or the files directory is not empty.
restore_insufficient_space Not enough disk space.
restore_reconcile_failed After loading: foreign key orphans or unbalanced journals were found. Drop and recreate the database before retrying.

After loading and checking, the restored copy is made safe before anyone signs in:

  • queue jobs pending or running become quarantined;
  • queued portal e-mail becomes cancelled with reason restore_quarantine;
  • undispatched outbox events are closed with restore_quarantine;
  • every session is revoked, every API credential is revoked and runtime tokens are deleted;
  • the configuration is set to environment staging, mail transport unconfigured and no integration bindings;
  • the maintenance gate stays engaged under the source backup's id.

Sign-in, sign-out and Return to pending work while the gate is held. Review the quarantined jobs on Backups, return only those that should still run, then release the gate. Internal communication e-mails and portal account notices are not quarantined; on the staging copy they cannot be sent because mail is unconfigured (see gaps file).

Fields

See Chapter 13, Installer: Archive, Staging URL.

Actions

Button/link What it does Permission Confirmation Result
Restore into this database /install/restore (POST) setup secret "Restore this archive into the saved database? The installation becomes restored staging." Redirect to /login; audit restore.verified
Return to pending /admin/backups/jobs/{id}/return (POST) system.backup.run "Return this job to pending? Cron runs it after the gate is released." Job pending

Rules and messages

  • "Only a quarantined job can be returned to pending." (409)
  • "{n} email notice(s) cancelled by restore. They are not sent again from this page."
  • "{n} outbox event(s) closed by restore. They are not dispatched again from this page."

Role differences

  • Only users with system.backup.run see Return to pending.

REC-03 Browser upgrade with pre-upgrade backup

Screen: Administration > Upgrade (/admin/upgrade); see Chapter 13.

Staging only: rehearse on a restored staging copy first. Writes pause while migrations run.

The upgrade itself does not take a backup. The pre-upgrade backup is an operator step: run a verified backup and confirm its off-server copy before uploading the new release (DEPLOYMENT_GUIDE "Upgrade"). Apply pending migrations then engages the gate, applies the pending migrations in order, reruns the essential seed (new permissions only; existing grants are not changed), records upgrade.applied and releases the gate. A failed migration stays pending, the gate is released and the failure is audited.

Fields

No fields.

Actions

Button/link What it does Permission Confirmation Result
Apply pending migrations /admin/upgrade (POST) system.backup.run "Apply {n} pending migrations? Writes pause while they run." "{n} migration(s) applied."

Rules and messages

  • "You cannot do that." (403)
  • "The database matches this application. There is nothing to apply."

Role differences

  • View with system.backup.view; apply with system.backup.run.

REC-04 Browser installer and safe configuration creation

Screen: /install; see Chapter 13, Installer.

Staging only: run on a new upload.

Safety measures in the code: the first visitor cannot claim the installation without the one-time storage/private/setup.secret; the environment is checked before the database form is accepted; the database step tests the connection before writing app/config.php; the finish step runs the migrations, creates the first company and administrator, marks the configuration installed, writes storage/private/install.lock and deletes the setup secret file. After that /install redirects to /dashboard. Interrupted installs: HOST_CHECKLIST T03.

Fields

See Chapter 13, Installer.

Actions

Button/link What it does Permission Confirmation Result
Continue, Test and save connection, Install /install/unlock, /install/database, /install/finish setup secret No Installed and locked

Rules and messages

  • "The setup secret is not valid." (403)
  • "Save the database connection first."
  • D20: errors are shown with HTTP 200 and raw text; the finish form is visible before the database is saved.

Role differences

  • None; no user exists yet.

REC-05 Health checks and support evidence collection

Screen: Administration > System health (/admin/health); panels in Chapter 13.

System health on a phone

Support evidence to collect when a user reports a problem (Developer-assisted):

  1. Reference: every error page ends with "Reference {correlation id}" and every response carries the header X-Correlation-Id. The machine API also returns X-Request-Id and request_id in the body.
  2. Audit log: filter Request id with that reference on /admin/audit; failed mutations are recorded with outcome Failed and a reason code. Export the range with Export CSV if needed.
  3. Technical log: storage/logs/technical.jsonl, one JSON line per unexpected error with time, correlation_id, type and message. Access needs the hosting file manager.
  4. API request log: the table api_request_log holds one row per machine API call with its request id.
  5. Health: a screenshot of /admin/health.
  6. Cron output: the lines printed by cron/run.php (SCH-01).

Redaction: audit values whose key contains password, passwd, secret, token, api key, authorization, cookie, mfa, encryption key, credential or session are stored as [redacted]; bank numbers are stored sealed and shown masked; the audit viewer additionally shows [restricted] for fields the viewer may not see. The technical log stores the exception message as is, without this redaction (see gaps file); share it only with the developer.

Fields

No fields.

Actions

Button/link What it does Permission Confirmation Result
Clear file cache /admin/cache/clear (POST) system.cache.clear "Clear the file cache? Cached data is rebuilt on the next requests." Cache emptied

Rules and messages

  • "Unknown means the measurement is not available. It is not a healthy result."
  • /admin/health has no permission check of its own (gaps file, Chapter 13).

Role differences

  • Panel action links show only with the related permission.

REC-06 Audit chain verification

Screen: Administration > Audit log (/admin/audit), panel Integrity; route /admin/audit/verify.

Audit integrity panel

Every audit event is chained to the previous one by a hash, and once a day cron signs a checkpoint with the integrity key. Cron verifies the next 5000 events per run; Verify next batch now does the same on demand. A changed, missing or unlinked event marks the verification failed and the health panel Audit and security critical ("Audit chain verification FAILED at sequence …"). A missing checkpoint key is reported as "The audit checkpoint key is missing from this server." Follow MAINTENANCE_RUNBOOK "Audit integrity alert"; never edit audit rows.

Fields

No fields.

Actions

Button/link What it does Permission Confirmation Result
Verify next batch now /admin/audit/verify (POST) system.audit.view No Integrity panel updated; audit audit.integrity_verified

Rules and messages

  • "You do not have permission to verify the audit log." (403)
  • "The audit chain has not been verified yet. Cron verifies it in batches."

Role differences

  • Any role with system.audit.view can verify.

Procedures

Check that cron runs (Developer-assisted)

  1. In the hosting panel confirm one cron entry for cron/run.php every minute with the PHP 8.1 binary.
  2. Run it once by hand from the hosting panel's cron "run now" (or wait a minute) and read its output.
  3. Open /admin/health and confirm Scheduler shows a recent heartbeat.

Prove the task lock (Developer-assisted, Staging only)

  1. Start two cron runs at the same time.
  2. One prints "Another cron run holds the scheduler lease. Nothing was done."
  3. On health, Overlapping runs refused increases by one.

Trace an e-mail that did not arrive (Developer-assisted)

  1. Find the row in portal_notifications or outbound_messages for the recipient.
  2. Read status, attempts, last_error and next_attempt_at. mail_transport_not_configured means no provider.
  3. Do not mark it sent by hand.

Restore drill on staging (Developer-assisted, Staging only)

  1. Follow REC-02 on a clean copy and record the time each step takes.
  2. Sign in, review Quarantined after restore, return only the needed jobs.
  3. Release the gate and check health.
  4. Record the data age and duration against the RPO and RTO once they are decided (Q20).

Collect support evidence

  1. Ask the user for the Reference from the error page and the time.
  2. Filter the audit log by Request id; export the events.
  3. Ask the developer for the matching technical.jsonl line.

Troubleshooting

Symptom Cause What to do
Health Scheduler unknown Cron never ran Set up the cron entry (Developer-assisted).
"Another cron run holds the scheduler lease. Nothing was done." every minute A run is longer than a minute or a lease is stuck Wait for the takeover; lower queue.batch_size if runs are killed.
"Maintenance gate is engaged. Cron work skipped." Backup running or restored staging Finish the backup or release the gate.
Imports stay queued, exports stay queued Cron not running or gate engaged As above.
E-mail rows end failed with mail_transport_not_configured No mail provider (this environment) Expected here; configure mail for live use (LIVE02/LIVE03).
Outbox event never dispatched Five handler failures Read last_error on the event; fix the cause (Developer-assisted).
"The last attempt has an unknown outcome. …" Unknown provider outcome Reconcile on /integrations (SCH-03).
Restore shows restore_target_not_empty Database not empty Drop and recreate the database, then retry.
Health Audit and security critical Chain verification failed Treat as a security incident; follow the runbook.

17. End-to-end walkthroughs

These thirteen walkthroughs follow a shipment from the first enquiry through operations, billing and the customer portal, using the synthetic Nile Freight demo company. Each one names the demo record that already shows that stage, then the clicks a user makes. Field rules, permissions and refusal messages are in the chapter linked from each walkthrough.

The pictures were taken from the running demo, not drawn. A picture that is missing was not captured: the reason is in SCREENSHOT_INDEX.csv. Nothing in this chapter was posted, closed, paid to a bank, or sent to a live provider while the manual was written.

Demo company: Nile Freight, base currency EGP. Second company, used only to show isolation: Delta Logistics (demo).

W01. Ocean export: enquiry to job

Spec §27.1. UML 09, 10, 42. Worked example: enquiry E-2026-000005, draft quotation Q-2026-000002, and the completed ocean export job HQ-OE-2026-000002 (customer reference DEMO-OCE-001, job id 2) for Acme Furniture Exports (demo).

Ocean export job DEMO-OCE-001

  1. Sign in and open Enquiries and leads. The Sinai enquiry E-2026-000004 has no quotation. The Lotus enquiry E-2026-000005 has draft quotation Q-2026-000002. Enquiry status stays open; quoting does not change it (chapter 3).
  2. Open Quotations. Q-2026-000002 is a draft (one charge line; the screen does not add further lines). Q-2026-000003 is issued. Q-2026-000004 is expired. Q-2026-000005 is accepted and produced booking B-2026-000002, customer reference DEMO-BKG-001, which was left as a booking and not turned into a job.
  3. On an issued quotation, Accept records the accepted quantity and the evidence (the demo uses "Customer email"). Accepting a revision whose validity date has passed marks it expired instead of accepted.
  4. From the acceptance, Book creates the booking. Confirming or cancelling a booking has no button (chapter 4).
  5. On the booking, create the job: branch, mode ocean, direction export, category forwarding. Job HQ-OE-2026-000002 is that result: status open, branch HQ, milestones booking confirmed and cargo received, task id 1 due 2026-11-18, packing-list document id 1, and custody record HQ-WR-2026-000001.
  6. Continue with W06 for the shared cost, W07 for the invoice (HQ-INV-2026-000001, 2,000 EGP, posted) and W08 for the receipt.

W02. Air import consolidation

Spec §27.2. UML 11, 55. Worked example: air import job HQ-AI-2026-000001 (id 6, customer reference DEMO-AII-001) and air export job HQ-AE-2026-000001 (id 5, DEMO-AIE-001).

Air workspace on the air import job

  1. Open the air export job and its Air tab. The demo has house air waybill 000-10000012 in draft, flight MS777 on 2026-11-25 from CAI to FRA, and air booking id 1.
  2. The air import job has milestone pre-alert received. Its shipment HQ-SH-2026-000005 is published to the Acme portal organization (W11).
  3. A master air waybill and a master-and-houses consolidation were not created by the demo seed. Air master (/air/masters/{id}) therefore has no record to open. Do not treat a missing screenshot of that screen as a picture of an empty master.
  4. Flight events, issue and cancel of a waybill, and reconciliation of a master are in chapter 6. Issue and cancel need operations.air.issue.

W03. Road trip with several deliveries

Spec §27.3. UML 11, 55. Worked example: road job PSD-RD-2026-000001 (id 7, DEMO-ROD-001, branch Port Said) and trip PSD-TRP-2026-000001 (id 1).

Road trip PSD-TRP-2026-000001

  1. Open Trips. Trip id 1 is assigned to the road job. The trip has its own consolidation (id 2), separate from the ocean consolidation in W06.
  2. Stops use Arrive, Pick up, Deliver, Fail and Redeliver (/trip-stops/{id}/...). A failed stop does not undo a stop already delivered. The demo trip was not progressed through those events, so the screen shows the trip as created.
  3. Shared trip cost is allocated on an approved basis. A redelivery cost belongs to the job that caused it. Details are in chapter 6.

W04. Customs, holds and cargo release

Spec §11, §27.1 step 8. UML 13, 52. Worked example: ocean import job ALX-OI-2026-000001 (id 4) and customs case ALX-CUS-2026-000001 (id 1, draft).

Customs case on the ocean import job

  1. Sign in as a customs coordinator (demo-customs@nile.test) and open the job's Customs tab. The case is a draft. Declaration lines, assessment and broker details are in chapter 7.
  2. Open Release on a job. Each hold is independent. Release is allowed only when every hold is cleared, or when someone with operations.release.override records an exception. The demo jobs were not put on hold and were not released, so this walkthrough stops at the draft customs case.
  3. Marking a customs payment paid does not post a journal. That is an open accounting question, not a completed payment.

W05. Cargo custody

Spec §11.2. UML 14. Worked example: custody record HQ-WR-2026-000001 (id 1) on ocean export job id 2.

Custody on the ocean export job

  1. Sign in as the warehouse user (demo-warehouse@nile.test) and open the job's Custody tab, or the Custody list.
  2. Record receipt of the cargo, then dispatch. Dispatch is not tied automatically to the quantity released on the Release tab (chapter 7). The demo record exists; it was not dispatched while this manual was written.

W06. Job costing and a shared consolidation

Spec §14. UML 17, 44. Worked example: consolidation HQ-CON-2026-000002 (id 3, master reference DEMO-CON-001), jobs DEMO-OCE-001 (Acme) and DEMO-OCE-002 (Lotus Textiles), carrier Blue Sea Lines (demo).

Consolidation DEMO-CON-001

  1. Open Consolidations. Consolidation id 3 is open, mode ocean, and contains one lot from each of the two jobs.
  2. The carrier estimate is buy line id 2, 800 EGP, approved and accrued. The split uses an approved basis (pieces, gross weight or chargeable weight). Approving the split does not require a second person; whether that should be maker-checker is an open policy question.
  3. The accrual is matched by supplier bill HQ-SB-2026-000001 in W09. Sell lines on DEMO-OCE-001 (freight 1,800 and documentation 200) were approved and invoiced in W07.
  4. A second company, Delta Logistics (demo), has its own job DEMO-DLTA-001 (id 8) and invoice CAI-INV-2026-000001. It does not appear in Nile Freight lists.

W07. Customer invoice and credit note

Spec §15. UML 35, 43. Worked example: posted invoice HQ-INV-2026-000001 (id 1, 2,000 EGP) and draft invoice id 5 (900 EGP, no number until it is posted) on the air import job.

Posted invoice HQ-INV-2026-000001

  1. On the job, Create invoice draft consumes the approved sell lines. Draft invoice id 5 is the unposted example.
  2. Post invoice writes the receivable and the journal in one transaction. The posted invoice cannot be edited. There is no second approver for invoice posting (open question Q11).
  3. PDF opens /invoices/1/pdf. The file is generated once and reused.
  4. A full credit of the Pharaoh Foods invoice (id 3, ALX-INV-2026-000001, 3,200 EGP) is credit note ALX-CN-2026-000001 (id 1, posted). The invoice status is credited. See W12.

W08. Customer receipt

Spec §15. UML 37, 45. Worked example: receipt HQ-RC-2026-000002 (id 2, reference RCPT-DEMO-ACME-FULL, 2,000 EGP, fully allocated to invoice id 1) and receipt HQ-RC-2026-000003 (id 3, RCPT-DEMO-LOTUS-PART, 1,000 of the 2,400 EGP Lotus invoice).

Receipt allocated to the Acme invoice

  1. Enter receipt. Enter the branch, customer, bank reference, gross amount and any bank charges withheld. Allocate to open invoices of that customer. Save the draft, then Post receipt. The demo receipts were drafted by one user and posted by another.
  2. A foreign-currency receipt needs a company exchange rate for the day and must be allocated in full. Unapplied foreign advances are refused.
  3. After posting, the remaining actions are apply unapplied cash, move an allocation, request a refund, and reverse. No live bank feed is connected.

W09. Supplier bill and payment batch

Spec §16. UML 44. Worked example: supplier bill HQ-SB-2026-000001 (id 1, supplier reference BSL-DEMO-800, 800 EGP, posted) and supplier payment batch id 1 (draft, 250 EGP, not released).

Posted supplier bill for the accrued 800 EGP

  1. New supplier bill. Match the bill line to the accrued buy line from W06, or enter a direct line. Post writes the payable and clears the accrual. A difference is a variance.
  2. New supplier payment. The draft batch was not released, exported, confirmed or failed. Release builds a file containing the supplier's IBAN and SWIFT; Download is the only output. There is no live bank connection, and confirming a batch must not be described as a bank payment.
  3. Beneficiary approval of the carrier's bank account is on the partner page. Approving it currently returns to the partner list (defect D11).

W10. Month-end reports and period close

Spec §17.4, §21. UML 19, 34. The November 2026 period (id 11, 2026-11-01 to 2026-11-30) is open. It was not soft-closed, hard-closed or reopened while this manual was written.

Reports index

  1. Open Reports. The seven reports are trial balance, profit and loss, balance sheet, receivable ageing, payable ageing, job profitability and tax. Each accepts a date range or an as-of date, and CSV (?format=csv). Definitions are in chapter 11.
  2. Open Period close, then the November period. Soft close allows only designated adjustments. Hard close refuses further posting. Reopen needs an approved request.
  3. Staging only: soft close, hard close and reopen change the books for every user. Run them only on a staging copy, and record the result in the test workbook. They are not shown here as completed.

W11. Customer portal

Spec §33. UML 21, 40, 46. Worked example: portal organization Acme Furniture Exports (demo) (id 2) and viewer demo-portal-viewer@example.test (Omar Farid).

Acme portal shipments

  1. A staff user with portal.organization.manage opens Customer portal, creates the organization from the customer profile, invites a member, grants a shipment and publishes an event. Grants: shipments HQ-SH-2026-000002 (ocean, booking confirmed) and HQ-SH-2026-000005 (air, pre-alert). An agent organization exists for Hamburg Forwarding Agent (demo) (id 3) with a grant on the ocean shipment only.
  2. The viewer signs in at /portal/login and opens Shipments. Only granted shipments are listed. A shipment that was not granted answers as not found.
  3. The portal administrator demo-portal-admin@example.test signs in with a password and opens the portal. The screenshots in this walkthrough use the viewer. Invitation and notification emails are English only (defect D18).

W12. Cancellations and late corrections

Spec §27.4. UML 31, 35.

Posted credit note ALX-CN-2026-000001

  1. Before anything is posted, cancel the draft (quotation revision, draft invoice, draft receipt, draft bill, draft payment batch) and give the reason the screen asks for. Cancelling a draft has no ledger effect.
  2. There is no decline action on a quotation. An expired quotation is one whose validity date has passed. Bookings have no cancel button.
  3. After posting, do not delete the document. Credit the invoice (the Pharaoh invoice was credited in full), reverse a receipt, or raise a supplier credit note. The original journal stays. A posted manual journal (JE-2026-000012, id 2, 50 EGP) is reversed by a new journal, not by editing it.
  4. A late cost after a period is closed is an open policy (question Q13): post into the open period, post a designated adjustment while the period is soft-closed, or reopen with approval. The demo period is still open, so none of those was done.

W13. Administration, health, backup and upgrade

Spec §19, §25, §26. UML 25, 41, 48. Sign in as admin@nile.test for users, audit and health. Company administrators do not see every administration screen.

System health

  1. Users and roles lists internal users and role assignments. There is no button to suspend a user (defect D13). Password reset creates a token and does not email it (defect D8).
  2. Audit log shows successful and failed changes. Verify checks the next block of the hash chain. The technical log is a separate file and is not this screen.
  3. System health reports the checks the application can run locally. It is not a live provider status.
  4. Staging only: Backups, releasing the backup gate, Upgrade and the installer (/install) lock or replace the application. During a backup the site answers with a plain-text 503 and sign-out is blocked (defect D19). Do not run them on the demo you are reading this from. The checks are in chapter 16 and are developer-assisted.

18. Appendix: APIs, webhooks and file formats (developer-assisted)

This appendix is the reference for integrators and testers: the company machine API /api/v1, booking intake, the portal API /api/portal/v1 used by the customer and agent portal, the inbound carrier tracking webhook, the CSV import templates, the supplier payment bank file and the portal export files. Everything here needs an HTTP client, a token or a signed request, so all steps are Developer-assisted.

Who uses it: integrators and developers. Credentials are issued by a Platform administrator or Company administrator (system.api.manage, Chapter 13) and intake tokens by a Sales manager or Company administrator (bookings.intake.configure). The portal API is used by signed-in portal users (Chapter 15). References: Spec §24 (APIs, imports and integrations), §25, §33.11; UML 23, 24, 47; acceptance A23, A25, P01, P06, P20, T17, T19.

No live provider is configured in this environment. All examples use the fake token <token>, the host https://erp.example.test and invented ids. Never paste a real token into a ticket, screenshot or this manual.

API credentials and the OpenAPI description

Open Administration > API credentials (/admin/api-credentials) to issue a credential (Chapter 13). The header link OpenAPI description opens /api/v1/openapi.json; /api/v1 returns the same document. Both are public and need no token.

OpenAPI description link on API credentials

Callout: (1) the OpenAPI description link.

The public OpenAPI description in the browser

API credentials in Arabic

The OpenAPI document (version 1.0.0, server /api/v1) lists /partners, /partners/{id}, /jobs, /jobs/{id}, /invoices and /tracking-events. It mentions booking intake but does not describe it, the webhook or the portal API; they are described below.

Fields

See Chapter 13 (Integration name, Expiry, Requests per minute, scopes[]).

Actions

Button/link What it does Permission Confirmation Result
OpenAPI description GET /api/v1/openapi.json none No JSON document

Rules and messages

  • The token is shown once: "Copy this token now. It is not shown again." Format fak_<8 hex>_<48 hex>; the first part is the Prefix shown in the list.

Role differences

  • Only system.api.manage can issue or revoke.

Machine API /api/v1

Base address: https://erp.example.test/api/v1. Every response is JSON, carries X-Request-Id and Cache-Control: no-store, and is logged in api_request_log.

Authentication and headers

Header Direction Rule
Authorization: Bearer <token> request Required on every endpoint except /api/v1 and /api/v1/openapi.json.
Idempotency-Key request Required on every write: 8 to 128 letters, digits, dashes, underscores, dots or colons.
Content-Type: application/json request Required on writes. Body up to 64 KB, one JSON object.
X-Request-Id both Optional on the request (8 to 64 letters, digits or dashes); otherwise generated. Always returned.
X-RateLimit-Limit, X-RateLimit-Remaining response The credential's per-minute limit and what is left.
Retry-After response With 429 and 503.
Idempotent-Replayed: true response A replay of an earlier write with the same key and body.
WWW-Authenticate: Bearer realm="api" response With 401.

A call acts as the user who issued the credential, inside the active company of the credential and limited to its scopes. Records of another company answer 404 "Not found.".

Error envelope

{"error": {"code": "unauthenticated", "message": "Send a bearer token."}, "request_id": "8430dd8c513a43ac22788803"}
HTTP code Message
401 unauthenticated "Send a bearer token." / "The token is not valid, has expired or was revoked."
403 insufficient_scope "This credential does not have the {scope} scope."
404 route_not_found "There is no such endpoint."
404 not_found "Not found."
405 method_not_allowed "This method is not supported here." (with Allow)
409 idempotency "This idempotency key was already used with a different request body." / "A request with this idempotency key is still being processed."
413 payload_too_large "The request body is limited to 64 KB."
415 unsupported_media_type "Send application/json."
400 invalid_json "The body is not valid JSON."
422 unknown_parameter Unknown query parameter.
422 invalid_parameter "page must be a positive number and per_page between 1 and 100."
422 unknown_field "Unknown field: {name}."
422 validation_failed Domain refusals, for example "Legal name is required.", "{field} has the wrong type."
428 idempotency_key_required "Send an Idempotency-Key header with every write."
429 rate_limited "Too many requests for this credential. Retry after the window resets." / "Too many failed authentication attempts from this address." (20 failures a minute per address)
503 maintenance "Writes are paused while a backup runs."
500 internal_error "The request could not be completed. Quote the request id to support."

The 401, 404 route_not_found and envelope above were observed with read-only GET requests on the local development server.

Endpoints

Method and path Scope Query parameters Returns
GET /api/v1/partners partners.read page, per_page (1–100, default 25), q, role, status (active, inactive, blocked), updated_since id, code, legal_name, trading_name, country_code, email, phone, status, updated_at, roles
GET /api/v1/partners/{id} partners.read none the above plus tax_id, website, service_types, payment_terms, contacts (name, email, phone, position_name). No bank details.
POST /api/v1/partners partners.write none 201 {"data": {"id", "code"}}
GET /api/v1/jobs jobs.read page, per_page, status, mode, direction, opened_from, opened_to, updated_since id, reference, mode, direction, category, operations_state, documentation_state, billing_state, opening_date, customer {id, code, name}, updated_at. No costs or margins.
GET /api/v1/jobs/{id} jobs.read none the above plus shipments with milestones (code, sequence_no, status, occurred_at, source)
GET /api/v1/invoices invoices.read page, per_page, customer_id, date_from, date_to, open_only (true/false) posted invoices: id, number, date, currency, total, tax_total, open_amount, job_reference, customer
GET /api/v1/tracking-events tracking.read page, per_page, job_id, since id, provider, event_id, job_id, reference, event_code, occurred_at, location, description, effect

List responses: {"data": [...], "meta": {"page", "per_page", "total", "next_page"}, "request_id"}. Amounts are decimal strings.

POST /api/v1/partners body fields: legal_name (required), trading_name, name_ar, registration_id, tax_id, country_code, phone, email, website, sector, language (en or ar), roles (array of role codes).

POST /api/v1/partners HTTP/1.1
Host: erp.example.test
Authorization: Bearer <token>
Idempotency-Key: partner-import-0001
Content-Type: application/json

{"legal_name": "Example Trading Co", "country_code": "EG", "roles": ["customer"]}

/api/v1/health

/api/v1/health is not part of the machine API. It is served inside the staff session: a signed-in staff user gets {"status": "ok"}, and a request without a session is redirected to /login (observed: HTTP 302). It cannot be used by an external monitor with a bearer token (see gaps file).

Booking intake API

Endpoint: POST /api/v1/intake/bookings. Tokens are issued per customer on Sales > Booking intake (/intake), panel API tokens: choose Customer, enter Token name (3 to 128 characters) and click Issue token; the token (fit_<8 hex>_<48 hex>) is shown once ("Copy this token now. It is shown once and cannot be recovered:"). Revoke posts to /intake/tokens/{id}/revoke ("Revoke this token? Requests using it will be refused.").

Booking intake API tokens panel

Request: Authorization: Bearer <token>, Content-Type: application/json, optional Idempotency-Key (1 to 255 visible ASCII characters), body up to 64 KB.

Field Required Rule
client_reference Yes 1 to 128 letters, digits, dot, dash, underscore or slash. Unique per token.
origin, destination, commodity Yes String or number, up to 255 characters.
mode, incoterm No Up to 255 characters.
ready_date No YYYY-MM-DD.
pieces, gross_weight_kg, volume_cbm No Non-negative decimal.
notes No Up to 4000 characters.

Responses: 202 {"intake_id", "status", "duplicate": false, "correlation_id"}; a repeat of the same request answers 200 with "duplicate": true. Errors use a flat envelope, different from /api/v1:

{"error": "unauthorized", "message": "The intake token is missing, unknown or revoked.", "correlation_id": "req_..."}
HTTP error Message
401 unauthorized "The intake token is missing, unknown or revoked."
405 method_not_allowed "Use POST."
415 unsupported_media_type "Send application/json."
413 payload_too_large "The request body is limited to 64 KB."
400 invalid_json "The body is not valid JSON."
422 validation_failed "Send one JSON object.", "client_reference is required: 1 to 128 letters, digits, dot, dash, underscore or slash.", "{field} is required.", "{field} must be a non-negative decimal.", "ready_date must be YYYY-MM-DD.", "Idempotency-Key must be 1 to 255 visible ASCII characters."
409 client_reference_conflict "This client_reference was already used for a different request."
409 idempotency_conflict "This Idempotency-Key was already used for a different request."
503 maintenance "Writes are paused while a backup runs."

Fields

Field Required What to enter / rules
Customer (profile_id) Yes The customer the token belongs to.
Token name (name) Yes 3 to 128 characters, for example "TMS integration".

Actions

Button/link What it does Permission Confirmation Result
Issue token POST /intake/tokens bookings.intake.configure No Token shown once
Revoke POST /intake/tokens/{id}/revoke bookings.intake.configure "Revoke this token? Requests using it will be refused." Token refused from then on

Rules and messages

  • "Name the token (3 to 128 characters)." / "Customer profile was not found." (404)

Role differences

  • The API tokens panel is hidden without bookings.intake.configure.

Portal API /api/portal/v1

Base path /api/portal/v1. Used by the portal pages; integrators cannot use it with a token. Authentication is the portal browser session cookie; a bearer token is ignored. Writes (POST, PUT) need the session CSRF token in the header X-CSRF-Token (returned by /context as csrf_token). Anonymous calls get 401; staff accounts get 404. A record that is not the active organization's answers with the same 404 as one that does not exist. Responses carry Cache-Control: private, no-store.

Envelope: success {"data": ..., "meta": {...}}; error {"error": {"code", "message"}} (no request id).

Method Path (after /api/portal/v1) Body / parameters Result
GET /context none user, organizations (membership_id, organization, type, role), active_membership_id, capabilities (download, write, upload, billing, export, administrator), csrf_token
POST /context/switch {"membership_id": 12} new active membership, capabilities and a new csrf_token
GET /dashboard none dashboard figures and unread_notifications
GET /shipments q, mode, stage, view, sort, page, per_page rows with meta
GET /shipments/{id} none shipment, fields, lots, references, freshness
GET /shipments/{id}/tracking none timeline, route, lots, freshness
GET /shipments/{id}/documents none documents, uploads, open document requests
GET / POST /shipments/{id}/enquiries POST {"category", "subject", "body", "change_type"} list, or 201 {"id"}
POST /shipments/{id}/uploads multipart: file, category, optional request_id, enquiry_id; header Idempotency-Key 201 (200 when replayed)
GET /documents/{id}/download none the file (attachment)
GET /uploads/{id}/download none the file (attachment)
GET /enquiries/{id} none enquiry with messages
POST /enquiries/{id}/messages {"body"} 201 {"message_id"}
POST /enquiries/{id}/withdraw none {"status": "closed", "closed_reason": "withdrawn"}
GET /notifications none id, title, shipment, created_at, read
POST /notifications/read {"ids": [1, 2]} {"marked": n}
GET /subscriptions none per-shipment subscriptions
PUT /subscriptions/{id} {"events": [...], "email": true} the updated subscription
GET /billing/invoices none invoices (only with billing access, otherwise 404)
GET / POST /exports POST {"type": "shipments"} or "invoices" list, or 202 {"id", "status": "queued"}
GET /exports/{id}/download none CSV file
GET /enquiry-categories none categories and change types

The shipment routes match the pattern /shipments/{id}(/tracking|/documents|/enquiries|/uploads)?.

Enquiry categories: status_request, document_request, cargo_detail_question, delivery_coordination, issue_report, change_request (with change_type routing, parties, delivery or cargo). Upload categories: customer_instructions, commercial_invoice, packing_list, certificate_origin, insurance, proof_of_delivery; PDF, JPEG or PNG up to 20 MB; at most 30 uploads an hour.

Fields

No staff screen. Portal screens are in Chapter 15.

Actions

Button/link What it does Permission Confirmation Result
/documents/{id}/download Downloads a published document membership role other than viewer, document published and not on hold No File; audit portal.document.downloaded or portal.document.download_refused
/uploads/{id}/download Downloads one of the organization's uploads membership No File; audit portal.upload.downloaded or portal.upload.refused
/exports/{id}/download Downloads a ready export export right on the membership No CSV; audit portal.export.downloaded or portal.export.download_refused

Rules and messages

HTTP code Message
401 unauthenticated "Sign in to the portal first." (observed on the local server)
419 csrf "The CSRF token is missing or not valid."
403 no_membership "Your sign-in has no active portal organization."
409 organization_not_selected "Choose an organization first with POST /context/switch."
404 not_found "Not found." / "The shipment was not found." / "The enquiry was not found." / "Organization was not found."
422 validation_failed "Choose a category." / "Choose what you want to change." / "Send the file as multipart field \"file\"." / "The upload needs a request key of up to 128 letters, digits, dots, dashes or colons." / "Choose shipments or invoices."
403 upload_not_permitted "Your role can view shipments but not upload files."
409 upload refusals "Uploads are not turned on for your organization. Answer a document request or contact customer service.", "Only PDF, JPEG and PNG files can be uploaded.", "The file is larger than the limit.", "Choose one of the listed document types."
429 rate_limited "Too many uploads. Try again later." / "You already have exports being prepared. Wait for them to finish."
404 not_available "The document is not available." / "The file is not available." / "The export is not available."
403 download_not_permitted "Your role can see this document but not download it. Ask a collaborator in your organization for a copy."
409 document_on_hold "This document is on hold. Please contact customer service."
409 enquiry_closed "This enquiry is closed. Open a new enquiry for a new question." / "This enquiry is already closed."
403 not_requester "Only the person who asked or an organization administrator can withdraw this enquiry."
403 export_not_permitted "Exports are not enabled for your account. Ask your organization administrator."
403 billing_not_permitted "Billing is not available for your account."
413 payload_too_large "The request body is too large."
400 invalid_json "The request body is not valid JSON."
500 server_error "The request could not be completed."

While the maintenance gate is engaged, portal writes get the plain-text 503 page (D19), not this JSON envelope.

Role differences

  • Viewer memberships ({PORTAL_VIEWER}) cannot download documents, write enquiries or upload; capabilities shows this. Billing and export depend on the membership flags (Chapter 14).

Tracking webhook

Endpoint: POST /api/v1/webhooks/tracking/{profile}, where {profile} is the tracking profile name bound to exactly one company (provider tracking.generic) with a webhook_secret of at least 16 characters. The webhook address is shown on Integrations > Bound profiles. In this environment no profile is bound, so every call answers 404 unknown_profile.

Signing: the sender computes HMAC-SHA256(webhook_secret, "{timestamp}.{raw body}") and sends:

Header Value
X-Webhook-Timestamp Unix seconds; must be within 300 seconds of the server time.
X-Webhook-Signature sha256= followed by the hex digest.
Content-Type application/json

Body (up to 64 KB):

{"id": "evt-0001", "type": "tracking.event",
 "data": {"reference": "MSCU1234567", "code": "departed", "occurred_at": "2026-11-08T10:00:00Z",
          "location": "Alexandria", "description": "Vessel departed"}}

id: 1 to 128 letters, digits, dot, underscore, colon or dash; type must be tracking.event.

Responses use the /api/v1 envelope with request_id (whk-…):

HTTP Result
200 {"status": "accepted", "event_id", "effect", "request_id"}; a repeated event id gives "status": "duplicate" and changes nothing.
401 invalid_signature "The webhook signature is missing or does not match." / "The webhook timestamp is outside the allowed window."
404 unknown_profile "No tracking webhook is configured at this address."
405 method_not_allowed "Use POST." (observed with GET on the local server)
413 payload_too_large "The webhook body is limited to 64 KB."
422 invalid_payload "Send {\"id\", \"type\": \"tracking.event\", \"data\": {...}}."
422 invalid_event "The event id is missing or invalid.", "The tracking reference is missing or invalid.", "The event code is missing or invalid.", "The event time is not a valid ISO 8601 timestamp.", "The event time is more than a day in the future."
503 webhook_unconfigured "This tracking webhook is not enabled." / "This tracking webhook has no signing secret."
503 maintenance "Paused while a backup runs. Retry later."

Every delivery, accepted or not, is listed under Integrations > Inbound webhooks with its signature result; only a hash of the body is kept there. Live checks: LIVE01 in the live provider checklist.

Import CSV formats

Files for Master data > Imports (Chapter 12). UTF-8 (a byte order mark is allowed), comma separated, first row is the header with the column names below in any order, at most 1 MB and 2000 data rows (200 for opening balances). Get an empty file with the Template button (/imports/template/{type}.csv, types partners, buy_rates, opening_balances).

Import column templates

The imports page on a phone screen

Type Required columns Optional columns
Partners (partners) legal_name trading_name, name_ar, registration_id, tax_id, country_code, phone, email, website, sector, language, roles
Buy rates (buy_rates) charge_code, provider_name, origin, destination, mode, date_basis, charge_basis, currency, amount, valid_from, valid_to, explanation unit_code, minimum_amount, maximum_amount, priority, breaks
Opening balances (opening_balances) account_code, debit, credit description

Value rules:

  • Partners: country_code two letters; language en or ar; roles separated by semicolons (or |), for example customer;shipper; text up to 255 characters; email must be valid.
  • Buy rates: charge_code an active charge code; mode a lower-case code such as ocean; date_basis booking_date or departure_date; charge_basis one of fixed, per_shipment, per_document, per_piece, per_container, per_kg, per_chargeable_kg, per_cbm, per_revenue_ton, per_truck, per_km, per_day, per_unit; currency a seeded currency; dates YYYY-MM-DD with valid_to not before valid_from; breaks written min_quantity:unit_rate separated by semicolons, for example 0:12.50;100:11.00 (not allowed on flat bases).
  • Opening balances: account_code must exist and must not be the receivable (1100) or payable (2000) control account; debit and credit positive decimals with a dot; the file totals must balance.

Example (partners):

legal_name,country_code,email,roles
Example Trading Co,EG,ops@example.test,customer;shipper

Fields

See Chapter 12, Imports.

Actions

Button/link What it does Permission Confirmation Result
Template GET /imports/template/{type}.csv signed-in staff No Header row as <type>_template.csv

Rules and messages

  • "Template was not found." (404) for another type.
  • Bank statements for reconciliation use a different CSV with the columns date, description, reference and amount (signed; money in is positive): "The statement needs the columns date, description, reference and amount." (Chapter 9).

Role differences

  • None for the template.

Supplier payment bank file

Created by Export on a released supplier payment batch (/supplier-payments/{id}/export, Chapter 9, permission finance.supplier_payment.export) and downloaded from /supplier-payments/{id}/file. The file is written to private storage once; its SHA-256 is recorded and checked again on every download.

Format: CSV, comma separated, UTF-8, header row, one row per payment line, file name <batch number>.csv:

Column Content
batch Payment batch number
line Line number
payment_date YYYY-MM-DD
beneficiary Account holder
iban Full IBAN or account number (decrypted for the file)
swift SWIFT / BIC
bank Bank name
amount Decimal with the currency's minor units, for example 1250.00
currency Three-letter code
remittance Supplier bill number and supplier reference

Cells that start with =, +, -, @, a tab or a carriage return get a leading apostrophe (spreadsheet formula protection, A25). Each beneficiary must still be the approved account; otherwise the export is refused. A batch can be exported once: "Only a released payment batch can be exported, once." (409). The file contains full bank numbers: store and send it only through the bank's secure channel.

Portal export files

Built by cron from a portal export request (Chapter 16, SCH-06) and downloaded at /api/portal/v1/exports/{id}/download as text/csv; charset=UTF-8.

Type Columns
shipments reference, mode, direction, status, last_update_utc, origin, destination
invoices invoice_number, invoice_date, currency, total, open, status

The files contain only what the portal user may see on the screen. They are kept 7 days.

Procedures

Call the machine API (Developer-assisted)

  1. Issue a credential with the needed scope on API credentials; store the token in the integration's secret store.
  2. Send GET /api/v1/partners?per_page=10 with Authorization: Bearer <token>.
  3. Read X-RateLimit-Remaining and meta.next_page; request the next page with page.
  4. For a write, send POST /api/v1/partners with Idempotency-Key and the JSON body. Repeat the same request: the answer is the same with Idempotent-Replayed: true.

Send a signed tracking webhook (Developer-assisted, local or staging with a test profile)

  1. Bind a tracking profile with a webhook_secret in the private configuration of a test copy.
  2. Compute the signature over {timestamp}.{body} and send the POST with both headers.
  3. Check Integrations > Inbound webhooks shows the delivery as accepted; send it again and see duplicate.

Prepare an import file

  1. Download the template from Imports.
  2. Fill the rows using the value rules above; save as UTF-8 CSV.
  3. Upload and read the row results (Chapter 12).

Troubleshooting

Symptom Cause What to do
401 "Send a bearer token." Missing Authorization header Add Authorization: Bearer <token>.
401 "The token is not valid, has expired or was revoked." Wrong, expired or revoked token Issue a new credential.
403 "This credential does not have the … scope." Scope missing Issue a credential with that scope.
422 unknown_parameter A query parameter the endpoint does not accept Remove it; check the table above.
428 "Send an Idempotency-Key header with every write." Write without the key Add a unique key per logical request.
409 "This idempotency key was already used with a different request body." Same key, different body Use a new key for a new request.
429 with Retry-After Rate limit Wait the given seconds.
503 maintenance Backup or upgrade in progress Retry later.
Monitor gets 302 from /api/v1/health It needs a staff session Use /api/v1 (public) for a liveness check, or monitor /admin/health as a user.
Webhook 401 "The webhook timestamp is outside the allowed window." Clock difference over 300 s Fix the sender's clock.
Portal API 419 "The CSRF token is missing or not valid." Missing or old X-CSRF-Token Read csrf_token from /context again (it changes after /context/switch).
Import "Unknown column: …" Header name differs from the template Copy the header from the template.

Quick start by role

Sign in at /login with the demo email for your role. A correct password opens the dashboard. There is no code page. The password is held by the test lead and is not written in this pack. The second company, Delta Logistics (demo), is only for isolation checks.

Each line is the first place that role works. The full sidebar is in chapter 1.

Platform administrator

Account: browser-staff@nile.test (also holds Company administrator and Finance manager). admin@nile.test holds this role too, and does not accept the demo password used for the pictures.

Open Administration > System health. Backups and Upgrade are on the same menu for an account the page allows. In this demo, Backups and Upgrade answered Not allowed for browser-staff@nile.test. Health opened.

Security administrator

Account: demo-security@nile.test.

Open Administration > Users and roles to add an account, then Audit log to read what changed.

Company administrator

Account: demo-companyadmin@nile.test.

Open Dashboard. Day-to-day work is Customers, Jobs and shipments, and Administration > Customer portal. Users and the audit log are not on this role.

Branch manager

Account: demo-branch@nile.test.

Open Tasks and Jobs and shipments. The branch manager in the demo is limited to the Alexandria branch where the role says so.

Sales representative

Account: demo-sales@nile.test.

Open My queue, then Enquiries and leads. Your own commission statement is under Commissions.

Sales manager

Account: demo-salesmgr@nile.test.

Same screens as a sales representative, plus customer financials, commission approval, margin approval and booking-intake tokens.

Pricing officer

Account: browser-pricing@nile.test.

Open Quotations and Rates and tariffs. You can read enquiries. Creating one is refused.

Pricing manager

Account: browser-pricing-mgr@nile.test.

Same as a pricing officer, plus Administration > Audit log and margin approval.

Operations coordinator

Account: demo-ops@nile.test.

Open Jobs and shipments. Record milestones, tasks and the operational tabs. Some buttons on air and documents ask for a stronger permission and are refused.

Operations manager

Account: demo-opsmgr@nile.test.

Same screens as a coordinator. Approvals is where you decide requests your permissions allow.

Documentation officer

Account: demo-docs@nile.test.

Open a job and choose Documents. Tasks are on Workspace > Tasks.

Customs coordinator

Account: demo-customs@nile.test.

Open a job and choose Customs. The rest of the operations menu is the same as a coordinator.

Warehouse or dispatch user

Account: demo-warehouse@nile.test.

Open Road trips and a job's Cargo custody tab.

AR accountant

Account: browser-ar1@nile.test.

Open Invoices, then Receipts. Reports are under Reports. You cannot open a job page, so job financial close is not on your screen even though the role holds that permission.

AP accountant

Account: demo-ap@nile.test.

Open Supplier bills, then Supplier payments. Imports is under Master data.

Treasury user

Account: demo-treasury@nile.test.

Open Supplier payments to release a batch someone else drafted, then Bank reconciliation. You cannot open the partner page, so the Approve button for a bank account is not on your screen.

Finance manager

Account: demo-finmgr@nile.test.

Open Finance overview, Reports, and Approvals. Period close is under Finance. You also see Customers, Commissions, Jobs and shipments, and Customs.

Executive

Account: demo-exec@nile.test.

Open Dashboard, then Reports. Operational lists are not on this role.

Auditor

Account: demo-auditor@nile.test.

Open Administration > Audit log, then Reports. You can read. You cannot post.

Customer viewer

Account: demo-portal-viewer@example.test, organization Acme Furniture Exports (demo).

Open /portal/login. Shipments lists only what was shared. You cannot create an enquiry, upload a file, or download a document.

Customer collaborator

No separate demo account. The role can create enquiries, upload files and download published documents. The viewer account cannot.

Customer organization administrator

Account: demo-portal-admin@example.test.

Same portal as the viewer, plus Members. Billing and export are not switched on for this demo organization, so Invoices and Exports answer Not found.

Agent viewer

Account: demo-agent-viewer@example.test, organization Hamburg Forwarding Agent (demo).

Same portal shape as a customer viewer, for the shipments shared with the agent.

Agent collaborator

No separate demo account. The seeded role can create enquiries and upload files. Download is not in the role template. The running download check treats every non-viewer the same, which the gaps report records.

Glossary

Terms as they are used on screen and in this manual. The Arabic column is copied from the application's Arabic catalogues (resources/lang/ar/*.php); a dash means the catalogue has no entry for that exact English text, so the screen may show the term inside a longer translated phrase or in English. Definitions follow the specification (docs/reference/Freight_Forwarding_ERP_Specification.md v1.2, §2 and the module sections).

Term Arabic on screen Meaning
Accrual — A posted estimate of an approved supplier cost before the supplier bill arrives. The supplier bill is later matched against it and any difference is posted as a variance (Spec §14.1, UML 44).
Acceptance — The customer's acceptance of one issued quotation revision, with evidence (for example "Customer email"). A booking can only be created from an acceptance (Spec §6.3, UML 42).
Agent — An overseas or partner forwarder. Agents can have a portal organization with agent roles (Spec §16, §33).
Alert التنبيهات (Alerts) A system or user signal about a job, task or record that needs attention. Alerts can be acknowledged, snoozed or resolved.
Allocation التخصيص The split of a shared cost (for example a consolidation's carrier freight) across jobs, on an approved basis: pieces, gross weight or chargeable weight (Spec §14.2).
Approval الموافقات (Approvals) A request that another authorised user must decide. Maker-checker approvals refuse a decision by the person who made the request.
Audit log سجل التدقيق The append-only record of successful and failed changes, with actor, time, correlation reference and redacted before/after data, protected by a verifiable hash chain (Spec §19.4, UML 51).
Backup gate — While a backup or restore runs, the application refuses changes and shows a "service unavailable" message (see D19). Releasing the gate is an administrator action.
Bank reconciliation التسوية البنكية Matching bank statement lines to posted receipts and payments (Spec §20 of UML, §16).
Booking الحجوزات (Bookings) A confirmed service order created from an accepted quotation; it produces one or more jobs (Spec §7.1).
Booking intake استقبال الحجوزات The inbox for booking requests received by email or API before they are converted into bookings.
Branch الفرع An operating unit of a company. Jobs, receipts and payments belong to one branch.
Buy charge / cost line — A cost the company pays a supplier for a job or consolidation; it is estimated, approved, accrued and then matched to a supplier bill.
Chargeable weight — The weight used for pricing and allocation in air freight (the greater of actual and volumetric weight, as entered).
Chart of accounts دليل الحسابات The list of ledger accounts. Accounts can be activated or deactivated for date ranges; posted history is never deleted.
Claim المطالبات (Claims) A cargo loss, damage or delay claim recorded against a job (Spec §11.3).
Company — A legal entity with its own base currency, books and data. Users only see the companies they are members of; data never crosses companies except through intercompany documents.
Consolidation التجميع A grouped carrier movement that carries lots from several shipments, often of different customers and jobs (Spec §2, §8–10, UML 11).
Correlation reference — The reference shown on error pages and stored in the audit log for one request; quote it when asking for support.
Credit note إشعار دائن A posted document that reduces a posted invoice, fully or partly. Posted invoices are never edited (Spec §15).
Custody الحيازة Physical receipt, storage and dispatch of cargo by the company (Spec §11.2, UML 14).
Customs الجمارك Customs coordination: declarations, statuses and disbursements for a job (Spec §11.1).
Customer عميل A partner profile with the customer role in a company.
Debit note إشعار مدين A posted document that adds charges to a customer after an invoice has been posted.
Disbursement — An amount paid on the customer's behalf (for example customs duty) and recharged.
Draft مسودة A document that can still be changed or cancelled; it has no accounting effect.
Enquiry الاستفسار A request for a forwarding service from a customer or lead; the start of the sales process (Spec §5).
Exchange rate (FX rate) — A company rate for a currency pair and date, entered manually or imported from CSV; no live rate provider is connected.
Grant (portal) — An explicit permission that lets a portal organization see one shipment. Without a grant nothing is visible (Spec §33.3).
Hard close — A period state in which no posting is accepted. Reopening needs an approved request.
Hold تعليق / الإيقافات (Holds) A block on cargo release (for example a missing document or unpaid charges). Holds are independent: each must be released (UML 52).
House / master air waybill — / بوليصة الشحن الجوي الرئيسية The house AWB covers one shipment; the master AWB is the carrier's document covering a consolidation.
Intercompany المعاملات بين الشركات Service documents between two companies of the group; posted lines are never shared directly (Spec §16).
Invoice الفاتورة A customer invoice drafted from approved sell lines of a job and posted atomically to the ledger and receivables (UML 43).
Job الملف The internal operational and profit responsibility for one or more shipments, in one company and branch (Spec §2, §7.2).
Journal القيد A balanced set of ledger lines created by posting a document or a manual journal.
Lead — A prospective customer that can be qualified and converted.
Leg المراحل (Legs) One movement between two locations within a shipment's route. Note: the Arabic catalogue uses the same word for Milestones (see the gaps report).
Lot الدفعة The executable portion of a shipment (pieces, weight, volume) that can be assigned to a consolidation or trip.
Maker-checker — A control where the user who prepared a record cannot approve or post it.
Manual journal القيود اليدوية A journal entered by finance staff, drafted and then approved/posted by another user; it can auto-reverse on a date.
Milestone المراحل (Milestones) A recorded execution event (for example departed, arrived, delivered) with actual date and time.
Organization (portal) — The customer or agent entity in the portal, linked to one partner profile, with members, grants and publications.
Outbox — A MySQL table of outgoing messages (emails, integration calls) written in the same transaction as the business change and delivered by the bounded cron run.
Period close إقفال الفترات Soft close (only designated adjustments allowed) and hard close (no postings) of accounting periods (Spec §17.4).
Portal — The external website for customers and agents to follow granted shipments, download published documents and send enquiries (Spec §33).
Post / posted مُرحّل (Posted) Posting gives a document its accounting effect; a posted record is immutable and can only be corrected by a new document (credit note, reversal). (The single word "Post" on the staff portal-enquiry reply button means "post a message", نشر.)
Publication — Making a specific event or document visible to a portal organization; it can be revoked.
Purchase order أوامر الشراء (Purchase orders) A commitment to a supplier for a job cost, with approval.
Quotation عرض السعر A versioned commercial offer with charge lines, validity and terms. Issued revisions are never edited; a revision creates a new version (UML 31).
Receipt سند القبض Money received from a customer, allocated to invoices; any rest is unapplied (Spec §15, UML 45).
Recognition الاعتراف بالإيرادات Moving revenue and cost to the profit and loss according to the company recognition basis (Spec §17.2).
Release الإفراج Approved release of cargo to the consignee once all holds are released (UML 52).
Reversal معكوس (Reversed) A new journal that cancels the effect of a posted journal on a stated date.
RTL — Right-to-left layout used when the language is Arabic.
Sell line — A charge to the customer on a job; it must be approved before it can be invoiced.
Shipment الشحنة A customer consignment within a job, with parties, cargo, route and references.
Soft close — A period state in which only designated adjustments can be posted.
Supplier bill — A supplier's invoice entered and matched to accrued job costs or to direct lines, then posted.
Supplier payment batch مدفوعات الموردين (Supplier payments) A batch of payments to suppliers, released, exported as a file and confirmed with the bank reference. No bank is connected; the file is only downloaded.
Tariff التعريفات (Tariffs) Buy and sell rates and margin rules used to price quotations.
Task المهام (Tasks) A unit of work with owner, due date and checklist, created manually, from a template or by a milestone.
Trip الرحلات (Trips) A road movement with stops (pickups and deliveries).
Unapplied غير المطبّق The part of a receipt not yet allocated to an invoice.