CoolCMS API

WorkListFormTrait

Add/edit form of a job (`WorkList`): company, customer (Invoicer `Customer` picker plus contact select), name, note.

The customer is required for invoicing, not for saving. The skeleton (translator, back key, save/update/cancel buttons) comes from BasePresenter::createForm(). The client account (userId) lives on the customer (Customer.cms_system_users_user_id), not on the job.

The "Invoicing" section (addBillingFields()) has two optional selects, invoice type and currency. The customer profile is only a default: applyCustomerRefresh() prefills them after a customer is chosen (a stored copy, so later profile edits do not change an existing job) and the admin may override them. CompanyInvoiceGenerator reads the chain job, customer, company.

Single submit: after the job is saved, saveWorkListForm() also processes work sheets and items (WorkListSheetsFormTrait::persistSheetsAndItems()).

IMPORTANT: state and closedDatetime are never set here (#[ExternalReadonly] plus defaultSkipFields; archiving and restoring live in WorkListArchiveFormTrait). buildWorkListEntity() keeps the DB values on edit and the entity defaults on add.

Refresh prefill: after a customer is picked, picker.js clicks the hidden customerRefresh button (Naja), applyCustomerRefresh() loads the customer's contacts via setItemsCustomerContact() and redraws the workListForm snippet. Modeled on InvoicerPickerRefreshTrait.

Terminology: WorkList is a job, WorkSheet is a work sheet inside a job.

Table of Contents

Methods

createComponentEditForm()  : Form
isSupplyPlaceRelevant()  : bool
Tells whether the job involves work on immovable property (VAT place of supply).
addBasicFields()  : void
Adds company, name and internal note.
addBillingFields()  : void
Adds the "Invoicing" section: invoice type and currency directly on the job, both optional.
addClientFields()  : void
Adds the customer (picker, hidden `invoicerCustomerId`) and the customer contact (select).
addPickerRefreshButtons()  : void
Adds the hidden `customerRefresh` submit (`validationScope([])`, class `ajax`, `data-picker-refresh`), which `picker.js` clicks after a selection.
applyCustomerRefresh()  : void
Handles the customer refresh.
buildContactOptions()  : array<int, string>
Builds customer contact options as `id => "name2/street, city (email)"` (only the parts the contact has).
buildWorkListEntity()  : WorkList
Builds the job entity from form values.
contactLabel()  : string
loadCompanyForSupplyPlace()  : Company|null
Loads the job's company for its default supply kind; `null` when the job has none yet.
prepareCurrencyOptions()  : array<int, NotTranslate>
The name is already translated DB data, not a key, hence `NotTranslate`.
prepareInvoiceTypeOptions()  : array<int, NotTranslate>
saveWorkListForm()  : bool
Saves the form data; the redirect is handled by {@see \App\Core\Base\Admin\BasePresenter::createForm()}.
setBillingFieldsFromCustomer()  : void
Overwrites invoice type and currency with the values from the newly chosen customer's billing profile.
setItemsCustomerContact()  : void
Refills the contact select options for the customer and resets the selected value.
setWorkListFormDefaults()  : void
tr()  : NotTranslate
Translates a key in the form domain to a ready string for fields with `setTranslator(null)`.
translatedName()  : string
Returns the entity name (with `translations`) in the current language, falling back to the first non-empty one.

Methods

isSupplyPlaceRelevant()

Tells whether the job involves work on immovable property (VAT place of supply).

protected isSupplyPlaceRelevant() : bool

True when any work item has a supply kind with nature immovable_service or, for a job without items, when the company's default supply kind has it (that is what such work is eventually invoiced with).

Only a display condition of the "supply place country" field; the value is always saved, so switching the supply kind back restores access without loss.

Return values
bool

addBasicFields()

Adds company, name and internal note.

private addBasicFields(Form $form) : void
Parameters
$form : Form

addBillingFields()

Adds the "Invoicing" section: invoice type and currency directly on the job, both optional.

private addBillingFields(Form $form) : void

An empty value is a signal for the next link of the chain (customer, company; CompanyInvoiceGenerator), hence setNullable(), not setRequired(). checkDefaultValue(false) is needed because the refresh handler changes the options and Nette would otherwise silently reject a default outside the current list.

Parameters
$form : Form

addClientFields()

Adds the customer (picker, hidden `invoicerCustomerId`) and the customer contact (select).

private addClientFields(Form $form) : void

The control lives in the form and the Picker component only renders it via {control picker-invoicerCustomerId}, so the template has no separate {input invoicerCustomerId}. Contact options are filled by the refresh handler and the edit defaults.

Without a customer the job cannot be invoiced (internal work uses the company's own customer). This is enforced at invoicing (WorkListManager::invoiceSheets() and the preview, code customerMissing), not on save, so older jobs without a customer stay editable.

IMPORTANT: if setRequired() is ever added here, note that HiddenField::addError() always redirects the error to the form level, so {inputError invoicerCustomerId} prints nothing; the message is shown only by BasePresenter::flashFormOwnErrors().

Parameters
$form : Form

addPickerRefreshButtons()

Adds the hidden `customerRefresh` submit (`validationScope([])`, class `ajax`, `data-picker-refresh`), which `picker.js` clicks after a selection.

private addPickerRefreshButtons(Form $form) : void
Parameters
$form : Form

applyCustomerRefresh()

Handles the customer refresh.

private applyCustomerRefresh(Form $form) : void

Loads the contacts of the newly chosen customer into invoicerCustomerContactId and resets its value (the old contact may not belong to the new customer), prefills invoice type and currency from the customer's billing profile (setBillingFieldsFromCustomer()) and redraws the workListForm snippet. It does not touch any client account, which lives on the customer.

Parameters
$form : Form

buildContactOptions()

Builds customer contact options as `id => "name2/street, city (email)"` (only the parts the contact has).

private buildContactOptions(Customer|null $customer) : array<int, string>
Parameters
$customer : Customer|null
Return values
array<int, string>

buildWorkListEntity()

Builds the job entity from form values.

private buildWorkListEntity(ArrayHash<string|int, mixed> $values) : WorkList

IMPORTANT: state and closedDatetime are never set here; they are #[ExternalReadonly] plus defaultSkipFields and change only through the archive actions.

Parameters
$values : ArrayHash<string|int, mixed>
Return values
WorkList

loadCompanyForSupplyPlace()

Loads the job's company for its default supply kind; `null` when the job has none yet.

private loadCompanyForSupplyPlace(int|null $companyId) : Company|null
Parameters
$companyId : int|null
Return values
Company|null

prepareCurrencyOptions()

The name is already translated DB data, not a key, hence `NotTranslate`.

private prepareCurrencyOptions() : array<int, NotTranslate>
Return values
array<int, NotTranslate>

prepareInvoiceTypeOptions()

private prepareInvoiceTypeOptions() : array<int, NotTranslate>
Return values
array<int, NotTranslate>

saveWorkListForm()

Saves the form data; the redirect is handled by {@see \App\Core\Base\Admin\BasePresenter::createForm()}.

private saveWorkListForm(ArrayHash<string|int, mixed> $values) : bool

After the job is saved, the POSTed sheets field (work sheets and items) is processed too (WorkListSheetsFormTrait::persistSheetsAndItems()). Partial errors in individual rows or sheets do not fail the job: they are collected and flashed separately, and everything saved stays saved.

Parameters
$values : ArrayHash<string|int, mixed>
Return values
bool

setBillingFieldsFromCustomer()

Overwrites invoice type and currency with the values from the newly chosen customer's billing profile.

private setBillingFieldsFromCustomer(Form $form, Customer|null $customer) : void

Refresh is a deliberate customer choice, so even a different (e.g. proforma) value already on the job is replaced; the admin can override again afterwards. When the profile has no value, null is set and the prompt then means fallback to the company settings (chain in the generator).

Parameters
$form : Form
$customer : Customer|null

setItemsCustomerContact()

Refills the contact select options for the customer and resets the selected value.

private setItemsCustomerContact(Form $form, Customer|null $customer) : void
Parameters
$form : Form
$customer : Customer|null

tr()

Translates a key in the form domain to a ready string for fields with `setTranslator(null)`.

private tr(string $key) : NotTranslate

IMPORTANT: Nette getLabel() and Validator::formatMessage() always use the form's translator, not the per-control override. Without NotTranslate the finished text would be translated again as a key and Tracy would report a missing translation.

Parameters
$key : string
Return values
NotTranslate

translatedName()

Returns the entity name (with `translations`) in the current language, falling back to the first non-empty one.

private translatedName(object $entity) : string
Parameters
$entity : object
Return values
string
On this page

Search results