DevOp.DK.Plus exposes two layers of models, and each writable entity has exactly one request pattern.
Every entity is split into a lean portable class in DevOp.ERP.Models and a sealed DkPlus<Entity> subtype in DevOp.DK.Plus.Models. A member is portable only when other ERP providers clearly have it with the same meaning; everything else is on the subtype. Promoting a member to the portable base later is a compatible change, while demoting one would break consumers, so uncertain members start on the subtype.
| Entity | Portable members (DevOp.ERP.Models) |
DK Plus subtype | Subtype additions (examples) |
|---|---|---|---|
| Customer | Number, Name, NationalId, Address, Email, Phone |
DkPlusCustomer |
VatNumber, IndustryClassificationCode, Gender, VatExempt, ReceivablesAccount, Group, PaymentTerm, Credit, InvoiceDelivery, ElectronicInvoicing, ObjectDate, and the read-only Contacts, Receivers, Memos, Changes, Attachments |
| Vendor | Number, Name, NationalId, Balance |
DkPlusVendor |
address, contact numbers, payment defaults, VatExempt, PayablesAccount, Approvers, BankAccount, Dimensions, Delivery, ObjectDate, Memos, Attachments |
| Product | ItemCode, Description, SecondaryDescription, UnitPrice, UnitPriceWithTax, PurchasePrice, QuantityInStock, Inactive |
DkPlusProduct |
Group, UnitCode, CostPrice, PricingDetails, InventorySettings, AccountingSettings, Barcodes, Warehouses, Units, ObjectDate |
| Employee | Number, Name, NationalId, Address, Email, MobilePhone, Status |
DkPlusEmployee |
Group, Supervisor, dimensions, time-clock and occupation fields |
| Project | Number, Name, Description, CustomerNumber, Status |
DkPlusProject |
Group, Type, Supervisor, Worker, Schedule, Requirements, BillingSettings, linked phases and tasks |
| Sales invoice, order, quote, recurring sale | Id, Number, Status, Customer, Reference, CurrencyCode, ExchangeRate, Date, Amount, AmountWithTax, TaxAmount, Lines |
DkPlusSalesInvoice, DkPlusSalesOrder, DkPlusSalesQuote, DkPlusRecurringSale |
warehouse, discounts, dimensions, payment term and mode, contact, receiver, settlement and claim data, ObjectDate; lines are DkPlusSalesDocumentLine |
| Sales person | Number, Name |
DkPlusSalesPerson |
EmployeeNumber, Warehouse, PriceGroup, closed-price flags |
Every portable entity also inherits ExternalId, Created, Modified, Deleted, and AdditionalData from ErpEntity.
Modified and ObjectDateModified is the dkERP modification time: the DK Plus Modified field (products: RecordModified), or null when DK Plus does not send it. ObjectDate (label "Stored Date", Icelandic "Vistað dags.") is a separate, read-only member on the DK Plus subtypes DkPlusCustomer, DkPlusVendor, DkPlusProduct, DkPlusSalesInvoice, DkPlusSalesOrder, DkPlusSalesQuote, and DkPlusRecurringSale, on v2 sales document lines, and on the work-request and project-journal models. It is the time dkPlus stored the record, it is not a modification time, and it is never substituted for Modified. Before 0.4.0 ObjectDate was read into Modified when Modified was missing; if you relied on that, read ObjectDate explicitly. DK Plus pages and applies the modified-after filter on ObjectDate for entities that have one, so a record whose Modified is older than a filter can still be returned; see Client API and queries.
IExternalErpClient returns the portable type. IDkPlusClient returns the subtype from every get, list, search, query, and paging member, so DK Plus code reads customer.VatExempt directly and portable code receives the same instances typed as Customer. Reference data and the General Ledger, inventory, warehouse, webhook, company, table, and My Pages models are single types without a subtype.
Related flat DK Plus fields are organized into objects on the subtypes: for customers Credit, InvoiceDelivery, and ElectronicInvoicing; for vendors Approvers, BankAccount, Dimensions, and Delivery; for products the pricing, inventory, accounting, sales, and point-of-sale settings; for projects Schedule, Requirements, and BillingSettings. DK Plus wire spellings such as Recivers, Dimmentions, and AllowNegativeInventiry stay internal.
Product warehouse rows are read as ProductWarehouseStock. The read models ProductWarehouse and WarehouseProductVariation were removed in 0.4.0 (see the 0.4 upgrade guide).
JsonSerializer.Serialize<Customer>(value) writes only portable members, but serializers that use the runtime type write every DkPlus* member. That includes ASP.NET Core output formatters, minimal APIs, and structured loggers such as Serilog {@Customer}, and the extra members include contact personal data and accounting codes. A portable consumer that returns or logs these objects must project them to its own DTOs. To read DK Plus data, inject IDkPlusClient or downcast to the DkPlus* subtype.
Swagger-enumerated DK Plus fields on the subtypes are C# enums with clean member names, for example Gender.Company for the wire value GenderCompany. CLI and JSON output print the member name.
DK Plus documents these fields as strings, but the live API returns most of them as their 0-based position in the documented list (for example "ItemClass": 0 for StockItem). From 0.3.1 the client accepts either form: a known name or an in-range number maps to the member. Any other value (an unknown name, an out-of-range number, or another JSON type) reads as null and the raw value is kept in AdditionalData under its wire name, so a read never fails and nothing is lost. DK Plus fields that the client exposes as plain strings, such as the portable Project.Status or Employee.Status, read a known number as its documented name (InProgress, Active).
Requests send the documented name. An undefined enum value on a request is rejected before any HTTP request is sent.
Each entity with a write endpoint has one pattern:
{Entity}CreateRequest is the full document. Its identity (Number or ItemCode) is required and carries the DK Plus constraints.{Entity}UpdateRequest is sparse. Every member is nullable, collections are IReadOnlyList<T>?, and there is no identity member: the identity is the path argument.CreateAsync(TCreateRequest, cancellationToken) and UpdateAsync(key, TUpdateRequest, cancellationToken).await client.Product.CreateAsync(new ProductCreateRequest
{
ItemCode = "ITEM-1",
Description = "Example item"
}, cancellationToken);
await client.Product.UpdateAsync(
"ITEM-1",
new ProductUpdateRequest { Description = "Renamed item" },
cancellationToken);
Requests exist for customers, customer contacts and item receivers, vendors, products, projects, project phases and tasks, work requests (WorkRequestCreateRequest, WorkRequestUpdateRequest), sales orders, quotes, recurring sales, sales people, vendor invoices (VendorInvoiceCreateRequest), General Ledger accounts and tax groups (create only), and, create only, sales invoices (SalesInvoiceCreateRequest). DK Plus length, range, and enum constraints are applied to the request models and validated before network I/O; see Request model validation.
When a nested read model carries read-only data (stock quantities, purchase history, nested modification times), the write side uses a dedicated {Element}Request type that leaves it out:
| Used in | Request type | Left out compared with the read model |
|---|---|---|
Product Barcodes |
ProductBarcodeRequest |
Modified |
Product Warehouses; Warehouse.UpdateProductAsync |
ProductWarehouseSettingsRequest |
stock quantities (QuantityInStock, QuantityOnBackOrders, QuantityOnPurchaseOrders), Modified |
Warehouse request Variations |
ProductVariationRequest |
stock quantities |
Product Units |
ProductUnitConversionRequest |
QuantityOnHand |
Product Vendors |
ProductVendorRequest |
LastPurchased* history |
| Customer, vendor, and product memo rows | MemoPageRequest |
Modified (RecordId is kept as the row match key) |
GeneralLedger.Account.CreateAsync |
GeneralLedgerAccountCreateRequest |
read-only Modified |
GeneralLedger.TaxGroup.CreateAsync |
GeneralLedgerTaxGroupCreateRequest |
read-only Modified |
Warehouse.UpdateProductAsync(warehouse, product, ProductWarehouseSettingsRequest, cancellationToken) takes the same type as product Warehouses; stock changes go through client.Inventory journals or counts.
Customer requests no longer carry Contacts, Receivers, or Memos, because DK Plus ignores them in the customer document. DkPlusCustomer still exposes them as read members. Contacts and item receivers are written through their own resources with CustomerContactCreateRequest/CustomerContactUpdateRequest and CustomerReceiverCreateRequest/CustomerReceiverUpdateRequest (client.Customer.Contact and client.Customer.Receiver); customer memos have no write endpoint and are read-only. A contact or receiver update request that sets no member throws ArgumentException before any request is sent. For the removed and renamed members see the 0.4 upgrade guide.
null means omitted, which means unchanged. The client never serializes a null member or a null extension-data value.new CustomerUpdateRequest { Alias = "" } sends "Alias":"". The client sends the empty string verbatim; DK Plus storing the empty value has been observed live for the customer Alias; other fields and entities have not been verified against a live company.null is never sent.Credit = new() { CreditLimit = 0 } sends only the credit limit.null is omitted and an explicit empty list sends [] on update. Whether a supplied list replaces or merges the stored collection on the server has not been verified for any entity.Merge behavior of the update endpoints has been observed live only for customers; product, project, vendor, sales-document, and sales-person updates rely on the client guarantee that only populated members are sent.
AdditionalData on a request is an escape hatch for DK Plus fields that have no typed member. Null entries are dropped, and a key that collides with a typed member is ignored so a stale extension value can never overwrite a typed value. On v2 sales documents the party, contact, carrier, and extra-details objects have their own AdditionalData, and unknown Dimensions and Payment members are kept in the document's AdditionalData under the wire name. Nested vendor settings, BankAccount, and project phase links also keep their own unknown members.
Portable and DK Plus model properties carry resource-backed DisplayAttribute labels in English and Icelandic, and records with a natural label expose AsString(). See Localization and globalization.