8.7 KiB
name, description
| name | description |
|---|---|
| crm-package-development | Use when creating a new Krayin CRM package or module, extending CRM functionality via a package, or adding custom business logic without modifying core files. Also use for any translation work — adding or moving lang files, adding a locale, or deciding whether a package gets its own Resources/lang directory — and whenever a fix or feature needs a CHANGELOG.md entry. Covers Laravel package structure, service providers, migrations, models, repositories, routes, controllers, views, localization, changelog entries, config, ACL, and menus. |
Skill: CRM Package Development (Krayin CRM)
Purpose
This skill guides the AI agent to design and develop a new CRM package (module) for Krayin CRM in a clean, upgrade-safe, and maintainable way.
When to Activate
Activate this skill when the user wants to:
- Create a new CRM package/module
- Extend CRM functionality via a package
- Add custom business logic without modifying core files
- Add, move, or delete translation (lang) files, or add a new locale
- Fix a bug or ship a feature — the change needs a
CHANGELOG.mdentry
Project Context
- Framework: Laravel
- Product: Krayin CRM
- Krayin CRM is already installed and running
- The package must integrate seamlessly with existing CRM modules
Development Rules
- Follow Krayin CRM architecture and conventions
- Do NOT modify core Krayin files unless explicitly required
- Use Laravel package-based structure
- All database changes must be done using migrations
- Ensure backward compatibility and safe upgrades
Package Structure Guidelines
A CRM package should follow this structure:
packages/
└── Webkul/
└── PackageName/
├── src/
│ ├── Providers/
│ │ └── PackageServiceProvider.php
│ ├── Models/
│ ├── Contracts/
│ ├── Repositories/
│ ├── Http/
│ │ ├── Controllers/
│ │ └── Requests/
│ ├── Routes/
│ │ ├── admin.php
│ │ └── api.php
│ ├── Database/
│ │ └── Migrations/
│ ├── Resources/
│ │ ├── views/ # only if the package renders its own Blade
│ │ └── lang/ # ONLY if views/ exists — see Localization
│ └── Config/
│ ├── package.php
│ ├── menu.php
│ ├── core_config.php
│ └── acl.php
└── composer.json
Localization (Translations)
The rule
A package owns a Resources/lang/ directory only if it also owns
Resources/views/ — that is, only if it renders its own Blade templates.
Every other package ships zero translation files; its strings live in the
Admin package under admin::app.*.
Before creating any lang file, ask:
Does this package have
src/Resources/views/with Blade templates?
- Yes → give it
Resources/lang/<locale>/app.phpand register its own namespace withloadTranslationsFrom()in the service provider. - No → do not create a lang directory and do not call
loadTranslationsFrom(). Put the keys in the Admin package and reference them asadmin::app.….
Current state of the repo — preserve this invariant:
| Owns views | Packages | Translations live in |
|---|---|---|
| Yes | Admin, Installer, WebForm |
the package itself (admin::, installer::, web_form::) |
| No | everything else (Core, DataTransfer, Lead, Contact, Quote, User, …) |
Admin (admin::app.*) |
"Backend-only" strings are still translations and follow the same rule —
validation-rule messages, importer error maps, and title values referenced
from Config/*.php all belong in Admin when the package has no views.
Where keys go for a view-less package
Nest them under the Admin key that already owns that domain so one feature's strings stay together. Established precedent:
| Before | After |
|---|---|
core::app.validations.code |
admin::app.validations.message.code |
data_transfer::app.importers.persons.title |
admin::app.settings.data-transfer.importers.persons.title |
data_transfer::app.validation.errors.system |
admin::app.settings.data-transfer.validation.errors.system |
When you move keys out of a package, finish the job: rewrite every reference,
delete the package's Resources/lang/, and remove its loadTranslationsFrom()
call so the provider does not point at a path that no longer exists.
Working with Admin lang files
- Admin ships one
app.phpper locale:ar,en,es,fa,ko,pt_BR,tr,vi,zh_CN. Add every new key to all of them in the same change — a key present only inenrenders as the raw key string in other locales. - If a real translation is unavailable, use the English text as the value. Never omit the key, and never leave the key name as its own value.
- Preserve placeholders exactly:
:attribute,:days,:count(Laravel) and%s(importer messages formatted withsprintf). - Pint enforces a single space around
=>(pint.json→binary_operator_spaces). Do not align=>into columns. - Name the file
app.phpin every locale directory. A misnamed file (e.g.ar/ar.php) is never loaded and the locale silently falls back to English.
Adding a new locale
- Create
<locale>/app.phpin each package that owns a lang directory (Admin,Installer,WebForm), mirroring theenkey structure exactly. - Register it in
config/app.php→available_locales. This drives both the admin locale dropdown (viaCore@locales) and the web installer. - Add it to
$localesinpackages/Webkul/Installer/src/Console/Commands/Installer.phpso the CLI installer offers it. - If the script is right-to-left, add it to the
['fa', 'ar']checks in the Admin layout Blade files.
Verifying
Confirm parity with en — same key set, same placeholders — then check that
keys actually resolve:
php artisan tinker --execute='app()->setLocale("<locale>"); echo trans("admin::app.<your.key>");'
./vendor/bin/pint --test packages/Webkul/<Package>
A key that resolves to its own name (admin::app.foo.bar) is missing.
Changelog
The rule
Every bug fix and every feature gets a CHANGELOG.md entry, in the same
change. Do not leave it for someone else and do not treat it as optional
follow-up work — a fix that is not in the changelog is invisible to the release
notes.
Add an entry when the change is user-visible or operator-visible: a bug fix, a new feature, an enhancement, a security fix, a configuration option, or a convention that affects how the product is built. Skip it only for changes with no observable effect at all — a typo in a code comment, a pure test refactor.
Format
Entries live at the top of CHANGELOG.md, newest version first, and use exactly this shape:
## **v2.2.5 (Upcoming)**
* #2601[fixed] Fixed lead value not saved when the currency is changed.
* #2604[feature] Added Chinese (Simplified) translation.
- One blank line between every entry — the file is read as rendered markdown.
- No space between the issue number and the tag:
#2601[fixed], not#2601 [fixed]. - Write the description as a complete sentence ending in a period, phrased from the user's point of view, not the implementation's.
Valid tags, and when each applies:
| Tag | Use for |
|---|---|
[fixed] |
Something was broken and now works. |
[feature] |
New capability that did not exist before. |
[enhancement] |
Existing behaviour improved, refactored, or made faster. |
[security] |
Vulnerability fix. Reference the CVE when one exists. |
Which version section
Add to the topmost section if it is still unreleased. If the top section is
already tagged *Release*, open a new one above it:
## **v<next patch version> (Upcoming)**
Replace (Upcoming) with the real date in the release commit — the existing
released headings show the expected style: ## **v2.2.4 (20th of July 2026)** *Release*.
Two things not to do
- Never invent an issue or PR number. They point at real GitHub PRs, so a
guessed number creates a false reference. If the PR does not exist yet, omit
the
#NNNNprefix and leave the tag, then fill the number in once it is opened. - Do not bump the version constant as part of a feature or fix.
KRAYIN_VERSIONinpackages/Webkul/Core/src/Core.phpis incremented in the release commit, together with dating the changelog heading — not per change.