Wiki

Wiki

Open in ChatGPT
Ask ChatGPT about this page
Open in Claude
Ask Claude about this page

Export Settings

Export Settings

Section: Setup

Export Settings is a Single DocType — there is exactly one record, accessible at Desk → Export Settings. It holds three categories of configuration: FX and CFA conversion parameters that flow into every costing, the Exporter Profile that appears on all printed documents and guest web pages, and Live Tracking credentials for vessel/container tracking.


FX & CFA Parameters

These fields govern how foreign-currency costs are converted and how the CFA destination block is calculated.

Field Fieldname Type Default What it controls
FX Buffer fx_buffer Float 1.0 Added to every base exchange rate to produce the buffered_rate
CFA Francs per EUR cfa_rate_per_eur Float (6dp) 655.957 XOF/XAF treaty peg used to convert EUR costs into CFA in the destination block
CFA Francs per USD cfa_rate_per_usd Float (6dp) 576 Informational USD-path CFA rate shown alongside the EUR path
Rate Staleness Threshold rate_staleness_days Int 14 Days after which a template charge rate is considered stale
Max Combinations max_combinations Int 500 Hard cap on the number of cost combinations the engine will generate

FX Buffer

When a costing is saved, set_exchange_rate_buffers runs across every row in the Exchange Rates child table:

row.buffered_rate = flt(row.base_rate) + flt(self.fx_buffer)

The buffered rate is used for cost conversion (what you pay): every foreign-currency charge in calculate_charges calls get_buffered_rate, so costs always convert at a rate that is 1.0 INR/unit higher than the live market rate by default. The base (unbuffered) rate is used for quote prices (what you charge the buyer): calculate_per_mt_costs and set_quoted_price both call get_base_rate. This asymmetry means the margin calculation is inherently conservative — the costing absorbs FX slippage before it becomes a loss.

Gotcha: The Desk form initialises Float fields to 0 on new Export Costing documents, which is indistinguishable from a deliberate zero on the server. get_export_settings is therefore whitelisted so the new-costing form can pre-fill fx_buffer via a server call rather than relying on field defaults set client-side.

CFA Francs per EUR (655.957)

The West and Central African CFA franc (XOF/XAF) is treaty-pegged to the EUR at 655.957 — this is not a market rate and does not float. The destination block converts EUR-denominated import duties and clearing costs into CFA using:

def get_destination_to_inr_rate(self) -> float:
    eur_rate = self.get_base_rate("EUR")
    if not (eur_rate and flt(self.cfa_rate_per_eur)):
        return 0.0
    return eur_rate / flt(self.cfa_rate_per_eur)

This gives INR-per-CFA, which then converts the destination per-bag charges (expressed in CFA) into the INR cascade.

Gotcha: Only change cfa_rate_per_eur if the client's bank charges a spread on top of the peg, or if the monetary union changes the treaty rate (historically once in 1994). The JSON description explicitly calls this out: "Change only if the client uses a bank rate or the peg changes."

CFA Francs per USD (576)

This is a market-derived rate used only for the informational USD-path landed cost displayed next to the primary EUR-path figure. It does not enter the INR cost cascade. Update it when the USD/XOF rate drifts materially.

Rate Staleness Threshold (14 days)

When populate_from_template copies charge rows from a Costing Template onto a new Export Costing, it compares each template row's rate_updated_on date against today:

is_stale = (
    not template_row.rate_updated_on
    or date_diff(today(), template_row.rate_updated_on) > settings.rate_staleness_days
)

Rows older than the threshold get rate_stale = 1 on the costing, which the UI can surface as a visual flag so the operator knows to re-fetch those rates before quoting. Rows with no rate_updated_on at all are always treated as stale.

Gotcha: This flag is set at template-copy time only. If you open an existing costing weeks later, the staleness is not rechecked — it reflects when the costing was created.

Max Combinations (500)

The combination engine cross-joins vendor rates across loading ports and container types to produce a matrix of possible cost scenarios. With many vendors and items this can explode. The cap prevents runaway generation:

# a zero cap would block all generation, so 0 falls back to the default too
max_combinations=cint(settings.max_combinations) or DEFAULT_MAX_COMBINATIONS

Note the special case: a value of 0 falls back to 500, not to zero. A true zero would block all generation and is assumed to be an unset field rather than a deliberate choice. If generation hits the cap, prune the number of vendor rate rows or raise this setting.


The get_export_settings Helper

@frappe.whitelist()
def get_export_settings() -> frappe._dict:

This is the canonical way to read Export Settings throughout the codebase. It uses frappe.get_cached_doc (no extra DB hit per call), then applies fallback logic for every numeric field:

  • fx_buffer, cfa_rate_per_eur, cfa_rate_per_usd, rate_staleness_days — fall back to the module-level DEFAULT_* constants when the field is None (i.e., the Single has never been saved with a value).
  • max_combinations — additionally treats 0 as unset and falls back to DEFAULT_MAX_COMBINATIONS.

The helper also returns Exporter Profile fields (exporter_name, exporter_address, iec_code, gstin, exporter_email, exporter_phone, bank_details) with no fallback — they pass through as-is and may be None if never filled in. company_logo is not included; it is only available via frappe.get_cached_doc("Export Settings") directly.

Callers: ExportCosting.validate (via set_defaults_from_settings), ExportCosting.populate_from_template, the combination engine, and the desk client for pre-filling new costings.


Snapshot Semantics — Settings Changes Never Rewrite Historical Costings

set_defaults_from_settings only writes the CFA rates onto a costing when they are currently zero:

def set_defaults_from_settings(self, settings):
    if self.fx_buffer is None:
        self.fx_buffer = settings.fx_buffer
    if not flt(self.cfa_rate_per_eur):
        # snapshot once; later settings changes must not rewrite old costings
        self.cfa_rate_per_eur = settings.cfa_rate_per_eur
    if not flt(self.cfa_rate_per_usd):
        self.cfa_rate_per_usd = settings.cfa_rate_per_usd

The comment in the code is explicit about the intent. Once a costing has been saved with a non-zero cfa_rate_per_eur, updating Export Settings will not touch it. This preserves the accuracy of historical costings — a margin calculated at 655.957 stays at 655.957 even if the bank rate is later updated to 660.

Note that fx_buffer uses a stricter guard (is None) than the CFA rates (not flt(...)): a costing where fx_buffer was explicitly set to 0.0 will not be overwritten, but one where the field was never touched (database NULL) will be seeded from settings.

The same snapshot pattern applies to import-duty lane parameters: populate_from_template snapshots them with the guard if not flt(self.get(fieldname)) and template.get(fieldname).


Exporter Profile

These fields are printed on every outbound trade document (Proforma Invoice, Commercial Invoice, Packing List) and injected into guest web pages. They describe your company to buyers and suppliers, and carry the legal identity fields required on Indian export documents.

Field Fieldname Type Appears on
Company Logo company_logo Attach Image Guest /quote/<token> page header
Exporter Name exporter_name Data Document headers, guest page header/footer
Exporter Address exporter_address Small Text Document headers
IEC Code iec_code Data Document headers (Indian customs identifier)
GSTIN gstin Data Document headers (GST registration number)
Email exporter_email Data (Email) Guest page "Questions?" line, document footer
Phone exporter_phone Data Guest page "Questions?" line, document footer
Bank Details bank_details Small Text Commercial Invoice footer (beneficiary, account number, SWIFT, AD code)

Company Logo and update_website_context

Guest portal pages (/quote/<token>) run outside an ERPNext session — there is no logged-in user. The logo and contact information are injected into every web page via a Frappe hook:

# export_management/website_context.py
def update_website_context(context):
    settings = frappe.get_cached_doc("Export Settings")
    context.update({
        "company_logo": settings.get("company_logo"),
        "exporter_name": settings.get("exporter_name"),
        "exporter_phone": settings.get("exporter_phone"),
        "exporter_email": settings.get("exporter_email"),
    })

This function is registered as the update_website_context hook in hooks.py. Frappe calls it before rendering any web page in the app, so every template that extends export_management/templates/layout.html automatically receives these four context variables. The layout's header include renders the logo and the contact line without any per-page plumbing.

Gotcha: company_logo is not included in get_export_settings — it is only available via frappe.get_cached_doc("Export Settings") directly. The helper exposes the other profile fields (name, address, IEC, GSTIN, email, phone, bank details) but omits the logo because it is not needed for costing calculations.

Use a WebP or PNG resized to approximately 96 px height for the logo. Oversized images will scale down in the browser but the raw file is still served, so resize before uploading.


Live Tracking

A third section in Export Settings holds vessel/container tracking credentials. These are separate from costing and Exporter Profile concerns.

Field Fieldname Type Default Notes
Tracking Provider tracking_provider Select Mock Mock yields deterministic demo data with no credentials; Maersk and Vessel AIS are dormant scaffolds that need the API keys below
Maersk API Key maersk_api_key Password Only read when provider is Maersk
AIS API Key ais_api_key Password Only read when provider is Vessel AIS
AIS API Base URL ais_api_base_url Data Base URL for the AIS provider endpoint

The Mock provider is safe for demos and development — it requires no credentials and returns predictable tracking events.


Access Control

Export Settings is restricted to the System Manager role (read, write, create, share, email, print). Non-manager users interact with it only indirectly: the costing form pre-fills from it via the whitelisted get_export_settings call, and guest pages receive the four website context variables automatically.

Export Settings screenshot

Last updated 3 months ago
Was this helpful?
Thanks!