feat(lang): full Polish translation, invoice qty, VAT labels, client NIP + password generator - #8
feat(lang): full Polish translation, invoice qty, VAT labels, client NIP + password generator#8hedon77 wants to merge 90 commits into
Conversation
… display) - ConfigController: customFields/store/update/destroy + validation - routes: admin/config/custom-fields* under manage_settings - view: admin/config/custom-fields.blade.php CRUD with add/edit modals - ClientController create/edit/show pass custom fields and persist values - CustomField model: clientFields(), valueFor(), options() - client create/edit forms render field by type; show displays values - nav + translations EN/PL (admin.custom_fields.*, nav, clients.custom_fields) - messages.success.custom_field_added/updated/deleted EN/PL
…shot - new invoices column buyer_custom_fields (json) - buyerSnapshotFrom copies fields flagged show_on_invoice (via CustomField::invoiceSnapshot) so later client edits don't rewrite already-issued invoices (issue Panelica#7) - Invoice::buyerCustomFields() falls back to live values for invoices issued before the column existed - shown in admin invoice view and PDF (frozen at issue time)
- admin client create form gains Password + Confirm Password (optional) - store() creates the linked User (owner) with the hashed password, so a client added by staff can sign in to the client area and be impersonated - email validated unique against both clients and users - new keys common.form.password / form.password_confirm, admin.clients.password_optional (EN/PL)
- admin client edit form gains New Password + Confirm Password (blank leaves it unchanged) - update() calls setClientPassword() which updates the owner user or creates one if the client has no linked login yet - new key admin.clients.password_optional_edit (EN/PL)
…er to client form
- Route gateway labels through payment_method_label() helper in client invoice/checkout, admin gateways config, and gateway logs - Translate BankTransferModule payment form (details, bank fields, reference, amount, notes) via messages.banktransfer.* keys - Translate raw payment_method/gateway values in order emails and admin order/domain/service/transaction/affiliate views - Add banktransfer.* keys to pl and en messages.php
Lang::has('payment_method.x') looks up the 'payment_method' file, but
the keys live in messages.php, so the helper always fell back to
ucwords() and showed 'Banktransfer' instead of 'Przelew'.
…ne in the invoice view
…in payment method select
…er default payment method
…(MariaDB offset needs limit)
|
Thank you for this — 4,550 keys across 13 files is a serious piece of work, and the quality checks came back strong. We reviewed it the same way we review our own changes, so here is exactly what we ran and what we found. What passed
The placeholder result is the one we care about most — a translation that silently drops One blocker
The other 12 files are correct ( One small gap
Fix those two and we'll merge — everything else is ready as-is. One thing worth saying up front, so it isn't a surprise later: PNLCS is under active development and new features keep adding new keys — this week alone we shipped buyer snapshots on invoices and the first phase of Docker app hosting, both of which introduced strings. A translation is therefore not a one-off: Thanks again for taking this on. 🇵🇱 |
…kage key in Polish
|
Thank you for the thorough review — the placeholder check and the byte-level look are exactly the kind of rigor that catches real problems. Both issues are fixed:
On the translation drift: I'd welcome being flagged as new keys land so I can keep |
|
Both fixes confirmed, and the re-check came back clean. Thank you for turning these around so quickly. Verified byte by byte
The One blocker: three write routes are behind a read permission In
You clearly know the distinction, because One note, not a blocker:
Happy to take the PR as soon as the permission group is moved; the note above can be a follow-up if you would rather keep this one focused. On keeping
English, Chinese and Turkish are in; Polish is not, so those strings currently fall back to English. No rush at all — flagging it because you asked to be told. |
…sort, module fields
…lientFields trait
|
Thank you — genuinely. Ninety commits over five days, a complete Polish locale, and a set of billing features we had left half-built is a serious amount of work, and it is work that every hosting company running PNLCS will benefit from. You are credited permanently. Your name and profile now sit in the Contributors section of the project's site at pnlcs.com — not as a changelog line that scrolls away, but as a fixed part of the page. Both points from the last review are resolved — verified
What this branch gives the projectWe checked each of these against
One item we cannot merge as it stands: the
|
Summary
Localization and billing/clients improvements for the PNLCS panel. PR includes full Polish translation of the admin and client areas plus several feature fixes discovered while testing the translation.
The invoicing work in particular aims to adapt the billing to our Polish legal requirements (split VAT rates, per-line VAT, PDF on the invoice/reminder emails, payment-due terms).
Changes
1. Full Polish translation
lang/pl/*)form.companykey and fixed client name labelbanktransfer→ Przelew) via a newpayment_method_label()helper; EN keeps "Bank Transfer"2. Invoice builder — quantity (qty)
qtycolumn (int, default 1) toinvoice_itemsInvoiceService::addLineItem()acceptsqty,recalculateTotals()multipliesamount × qtyitems.*.qty(int, min 1)3. Invoice label fixes
pdf.tax,admin.invoices.tax,common.table.tax)VAT (23%)instead ofVAT (23.00000%))" zł"with a leading space)4. Custom client fields + buyer snapshot
buyer_custom_fields)5. Client login accounts
admin_only=false)6. Client form — NIP + password generator
tax_id) field on client create/edit forms and on the summary view7. Client phone — country-driven prefix
phone_prefixcolumn on clients (varchar 4) withfull_phoneaccessor on the model8. Invoice view fixes
payment_method_label()(Przelew instead of rawbanktransfer)9. Client — default payment method
default_payment_methodcolumn on clientsNoneoption)10. General setting — default payment method for new clients
DefaultPaymentMethodsetting in System Settings (Ustawienia ogólne)banktransfer11. Client create form — password generator
12. Billing period on invoice items
Obsługa serwera VPS (Monthly) — test.pl — 2026/08/15 - 2026/09/15next_due_dateand the billing cycle, so itemized periods match the covered intervalObsługa serwera VPS (2026/08/15 - 2026/09/15) — test.pl13. Service — editable "Next Due Date"
PUT admin/services/{service}/next-dueroute (undermanage_servicespermission)next_due_date14. Configurable invoice numbering scheme
InvoiceNumberFormatsetting in System Settings (Ustawienia ogólne), displayed under "Default Payment Method for New Clients"{year},{yy},{month},{day}and{num}placeholders (e.g.INV-{year}{month}-{num}→INV-202608-000008){num}is padded to 6 digits and continues the sequence across format changes (never reissues a number)/or\(e.g.FA-2026/08-000008) still download15. Service — status management buttons
PUT admin/services/{service}/statusroute (updateStatus) andDELETE admin/services/{service}route (destroy, undermanage_services)suspension_date/termination_date; theDeletebutton permanently removes the service (cascade cancels its addons)16. Semi-Annually next-due date fix
OrderService::calculateNextDueDate()matched the cycle assemi-annuallywhile the rest of the app (form, validation,BillingCycleHelper,Service::monthsInCycle) usessemiannually, so a Semi-Annually order got anext_due_dateone month out instead of sixcalculateNextDueDate()now delegates toBillingCycleHelper::advance(), the single source of truth for cycle lengths17. Client domain transfer with EPP code
GET client/domains/transferpage with a domain + EPP/Auth code form; the code is validated (required_if type=transfer) and stored on the cart itemepp_codecolumn ondomains(migration) and the value is persisted when the order is placedOrderService::registerOrderedDomain()now actually initiates a transfer through the registrar ($registrar->transfer($domain, $eppCode)) instead of leaving it "to be done by hand".plhad no WHOIS server configured)#links)18. HRD (hrd.pl) registrar
Modules\Registrars\HRD\HrdRegistrarwired to thehrd/hrd-apiPHP SDK (Composer VCS repoconsultingservice/hrd-api)api_login,api_hash,api_passand optionaldefault_ns_groupfor registrationsdomainCreate), transfer (domainTransferwith the auth code), renew, nameserver get/set, EPP code retrieval (domainTradeGetPw) andsyncDomainhrdinModuleServiceProvider; verified on the live server (SDK autoloads, module resolves with 4 config fields)19. Admin domain view (WHMCS-style)
syncDomain)DomainService::renewDomainpayment_method)domains.{sync,renew,nameservers,lock,autorenew,epp,registrar}20. Client domains
nameserverscolumn (was reading non-existentns1..ns5)21. Settings — Domains section
DefaultNameserver1..5)22. Settings — Invoices section
InvoiceDueDays, default 14)InvoiceService, the renewal generator and the admin "new invoice" form)23. Invoice builder — per-item VAT
tax_ratecolumn oninvoice_items)recalculateTotals()sums VAT per line from its own rate; legacy invoices without a per-line rate keep using the invoice-level rateVAT 23%,VAT 7%, …)24. Invoice view — inline editing
PUT admin/invoices/{invoice}/items/{item}route; editing a legacy line converts its siblings to explicit rates so the tax stays consistent25. Invoice view — send / remind
InvoiceCreatedMail); Remind sends a payment reminder (PaymentReminderMail, only for unpaid/overdue invoices)26. Invoice activity log
invoice_idcolumn onactivity_logs; events older than the 50 most recent per invoice are pruned automatically27. Invoice view — add / remove line items
POST admin/invoices/{invoice}/items)DELETE admin/invoices/{invoice}/items/{item}), matching the create form's look28. Invoice list — bulk actions
/admin/invoicesPOST admin/bulk/invoice-action)Testing
view:cache, translation cache refreshroute:listconfirms the new route is registeredINV-000007→ nextINV-202608-000008under the seededINV-{year}{month}-{num}formatFA-2026/08-000008→ fileinvoice-FA-2026-08-000008.pdf(HTTP 200)route:list)calculateNextDueDate()now advances six months viaBillingCycleHelper::advance()route:liston the live serverhrdregisteredInvoiceDueDayssetting verified:route:list+ admin "new invoice" form defaults from the settingtax_ratemigration applied, totals recalculated from per-line ratesroute:list(invoices.items.update,invoices.send,invoices.remind)invoice_idmigration applied, events pruned beyond 50 per invoiceroute:list(invoices.items.store,invoices.items.destroy,bulk.invoice-action)