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
createComponentEditForm()
protected
createComponentEditForm() : Form
Return values
FormisSupplyPlaceRelevant()
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
booladdBasicFields()
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
WorkListcontactLabel()
private
contactLabel(CustomerContact $contact) : string
Parameters
- $contact : CustomerContact
Return values
stringloadCompanyForSupplyPlace()
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|nullprepareCurrencyOptions()
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
boolsetBillingFieldsFromCustomer()
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
setItemsCustomerContact()
Refills the contact select options for the customer and resets the selected value.
private
setItemsCustomerContact(Form $form, Customer|null $customer) : void
Parameters
setWorkListFormDefaults()
private
setWorkListFormDefaults(Form $form) : void
Parameters
- $form : Form
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
NotTranslatetranslatedName()
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