# Cosmic Coding Conventions

> Base class usage, coding standards, and naming conventions for Kingdee Cosmic Java development.

---

## Base Class Usage

| Scenario | Base Class | Method to Override |
|----------|-----------|-------------------|
| Form plugin (general) | `AbstractFormPlugin` | `registerListener`, `afterCreateNewData` |
| Form plugin (Ext) | `AbstractFormPluginExt` (preferred over `AbstractFormPlugin` for new code) | Same as parent + Ext utilities |
| Bill plugin | `AbstractBillPlugInExt` | `registerListener`, `afterBindData` |
| List plugin | `AbstractListPluginExt` | — |
| Operation service (Ext) | `AbstractOperationServicePlugInExt` | `executeOnOpen`, `execute` |
| Operation service (native) | `AbstractOperationServicePlugIn` | Same as Ext |
| BOTP convert | `AbstractConvertPlugIn` (in `kd.bos.entity.botp.plugin`) | — |
| BOTP write-back | `AbstractWriteBackPlugIn` (in `kd.bos.entity.botp.plugin`) | — |
| Validator (Ext) | `AbstractValidatorExt` | — |
| Report plugin | `AbstractReportFormPlugin` | — |

**Rule**: New code MUST prefer the `kd.cd.common.plugin` Ext base classes (`*Ext`). Native base classes are acceptable only when Ext classes don't apply — explain why.

---

## Naming Conventions

| Element | Convention | Example |
|---------|-----------|---------|
| Extension project | `<isv>-<cloud>-<system>[-<type>]-ext` | `kingdee-cosmic-pay-ext` |
| Extension package | `<isv>.<cloud>[.<app>][.<feature>][.<suffix>]` | `kingdee.cosmic.pay.form` |
| Class | UpperCamelCase | `PayFormPlugin` |
| Method/variable | lowerCamelCase | `getPayStatus()` |
| Abstract class | `Abstract` prefix | `AbstractPayFormPlugin` |
| Exception class | `Exception` suffix | `PayBizException` |
| Enum class | `Enum` suffix | `PayStatusEnum` |
| Form plugin | `{Name}FormPlugin` | `PayFormPlugin` |
| Bill plugin | `{Name}BillPlugin` | `PayBillPlugin` |
| List plugin | `{Name}ListPlugin` | `PayListPlugin` |
| Operation plugin | `{Name}OpPlugin` | `SubmitOpPlugin` |
| Report plugin | `{Name}RptPlugin` | `PayRptPlugin` |

Service/DAO naming: `get` for single, `list` for collection, `count` for counting, `save/insert` for create, `remove/delete` for delete, `update` for modify.

---

## Framework Access Patterns

| What you need | Preferred approach |
|---------------|-------------------|
| UI control (readonly, enable, dialog) | `this.getView()` → `IFormView` methods |
| Data (get/set values, structure) | `this.getModel()` → `IDataModel` methods |
| EXISTS check | `QueryServiceHelper.exists(...)` — NEVER `queryOne(...) != null` |
| Batch operation | `OpUtils.executeOperateOrThrow(...)` |
| Single save/submit/audit | `OpUtils` (not `OperationServiceHelper` directly) |
| Multiple operations in chain | `OperateChain` |
| Base data query | `BusinessDataServiceHelper.loadFromCache(...)` first |
| DynamicObject value access | `DynamicObjectUtils` — NEVER deep chain `get("a.b.c")` |
| Data conversion | `BotpUtils` — NEVER hand-write `PushArgs`/`DrawArgs` |
| Attachment | `AttachmentUtils` + uploader — NOT scattered `AttachmentServiceHelper` |
| Base data query helper | `BaseDataServiceHelper` |
| String isBlank | `CharSequenceUtils` — fallback `org.apache.commons.lang3.StringUtils` |
| Collection isEmpty | `CollectionUtils` (`kd.cd.core.util`) — fallback `org.apache.commons.collections4.CollectionUtils` |
| Error aggregation | `OpUtils.addErrorMessage(...)`, `OpUtils.getCompleteFailMsg(...)` |
| BigDecimal math | `BigDecimalUtils.add()`, `subtract()`, `multiply()`, `divide()` |
| Primary key empty check | `EntityUtils.isEmptyPk(pk)` / `isNotEmptyPk(pk)` |
| Thread management | `kd.bos.threads.ThreadPools` — NEVER `new Thread(...)` or `Executors.*` |

---

## Lifecycle Event Rules

| Event | Allowed Actions | Forbidden Actions |
|-------|----------------|-------------------|
| `initialize()` | Minimal init | Register listeners, set UI state |
| `registerListener` | Register control events | Call `model.getValue(...)` (data not yet bound) |
| `beforeBindData` / `afterBindData` | Init UI state | Modify data packet (`setValue`) |
| `afterCreateNewData` | Data initialization | Expect `propertyChanged` to fire |
| `propertyChanged` | React to field changes | Unconditional `setValue` (causes infinite loop); check before assigning |
| Operation plugin | Operate on `DynamicObject` | Call `this.getView()`, `this.getModel().setValue(...)` |

---

## Anti-Patterns (Common Mistakes)

| Pattern | Why It's Wrong | Correct Approach |
|---------|---------------|------------------|
| `setReadOnly(...)` | Method doesn't exist | `getView().setEnable(false, "key")` |
| `afterCreateControl(...)` | Method doesn't exist | `afterBindData` / `registerListener` |
| `this.getView().refresh()` | Method doesn't exist | `this.getView().updateView(key)` |
| `model.getEntryCount(...)` | Method doesn't exist | `model.getEntryRowCount(entryKey)` |
| `model.deleteRow(...)` | Method doesn't exist | `model.deleteEntryRow(entryKey, rowIndex)` |
| `model.addRow(...)` | Method doesn't exist | `model.createNewEntryRow(entryKey, rowIndex)` |
| `model.getRowCount(...)` | Method doesn't exist | `model.getEntryRowCount(entryKey)` |
| `QueryServiceHelper.queryAll(...)` | Method doesn't exist | `QueryServiceHelper.query(entityId, selectFields, filters)` |
| `"Cosmic"`-prefix utility class | Mostly doesn't exist | Use `kd.cd.common.*` helpers |
| `"Cloud"`-prefix utility class | Mostly doesn't exist | Use actual project-specific classes |
| `BillHelper` | Doesn't exist | `BusinessDataServiceHelper` |
| `FormHelper` | Doesn't exist | `FormUtils` (`kd.cd.common.form`) |
| `ListHelper` | Doesn't exist | `BaseDataServiceHelper` or `QueryServiceHelper` |

---

## Project Structure (Recommended)

For Cosmic modules following the four-project convention:

```
<module-group>-common/         # Shared DTOs, constants, enums
<module-group>-business/       # Static helper classes, business logic
<module-group>-opplugin/       # Operation plugins (save, submit, audit, etc.)
<module-group>-formplugin/     # Form plugins, bill plugins, list plugins
```

Database table naming:
- Extension tables: `tk_<isv>_...`
- Extension columns: `fk_<isv>_...`
- Table key/column name length: ≤ 24 chars
- Column key: 4-24 chars, alphanumeric + underscore
- Amount/quantity: `Decimal` with explicit precision, NOT NULL, default 0
- No database foreign keys (use platform model instead)
- Total row byte length ≤ 8K (excluding LOB/Image/nText)
- New tables must have a physical primary key and a clustered index

---

## Minimal Examples

These examples show the expected shape only. For full templates and richer snippets, use the `ok-cosmic` skill assets.

### Form Plugin Shape

```java
public class PayFormPlugin extends AbstractFormPluginExt {
    private static final String FIELD_AMOUNT = "amount";

    @Override
    public void registerListener(EventObject e) {
        super.registerListener(e);
        this.addItemClickListeners("tbmain");
    }

    @Override
    public void propertyChanged(PropertyChangedArgs e) {
        if (!FIELD_AMOUNT.equals(e.getProperty().getName())) {
            return;
        }
        Object value = this.getModel().getValue(FIELD_AMOUNT);
        // Recalculate only when the new value differs from the current dependent value.
    }
}
```

### Operation Plugin Shape

```java
public class SubmitOpPlugin extends AbstractOperationServicePlugInExt {
    @Override
    public void execute(OperationServiceArgs e) {
        DynamicObject[] dataEntities = e.getDataEntities();
        if (dataEntities == null || dataEntities.length == 0) {
            return;
        }
        for (DynamicObject bill : dataEntities) {
            validateBeforeSubmit(bill);
        }
    }

    private void validateBeforeSubmit(DynamicObject bill) {
        // Throw a platform business exception when continuing would corrupt data.
    }
}
```

