Materials
The property tables that give a material its identity — substance, form, dimension, finish, grade, and type.
A material is an item of type Material: the raw stock you cut, form, and consume rather than a finished part. What makes it a material is a small set of structured property tables. Instead of typing "1/16-inch cold-rolled 304 stainless sheet" into a name field, you pick a substance, a form, a dimension, and optionally a finish, grade, and type. Those choices compose the material's identity and drive its readable name and code.
Each material row (material table) carries only foreign keys into these lookups: materialSubstanceId, materialFormId, dimensionId, finishId, gradeId, and materialTypeId. All six are nullable, so a material can be as loosely or tightly specified as you need.
The property tables
Six lookup tables compose a material. Two describe the material in the abstract (substance and form); the rest are scoped to one of those, so the choices cascade.
name and a code.name and a code.isMetric.name and a code.The lookups cascade
Substance and form stand alone. Grade and finish are picked from the list for the chosen substance; dimension from the list for the chosen form; type from the intersection of both. Change the substance and the grade or finish list you can pick from changes with it. This is why the material form on new-material screens fills top down.
Scope: system-wide or per company
The property tables ship with a starter taxonomy. Every lookup has a nullable companyId: rows with companyId null are system-seeded and visible to every company; rows with your company id are your own additions. You inherit a working set of substances, forms, and grades out of the box and extend it per company without touching anyone else's.
Uniqueness is enforced per scope so a system value and a company value never collide:
Substance and form carry more; the rest are lean
Only materialSubstance and materialForm have a code, custom fields, and audit columns. materialDimension, materialFinish, materialGrade, and materialType are lean lookups with no custom fields and no audit trail, and their seeded ids are human-readable (like steel-1018) rather than generated keys.
How a material references them
A material links to its item positionally, not through a foreign key. When you create one, Carbon inserts the item row first with type set to Material, then inserts the material row using the item's readable id as material.id. There is no itemId column. The two are joined on item.readableId = material.id within a company, so a material's part number is its material id.
Because the join is on the readable id, one material can span several item records: separate revisions, and a separate item per stock size. The sizes you assign become selectable variants on jobs and purchase orders, each its own item row sharing the material's id.
Only substance and form are required to generate a material
When Carbon generates the material and its ids, materialSubstanceId and materialFormId are required; dimension, finish, grade, and type are optional. A material is minimally "a substance in a form" and everything else refines it.
Naming and uniqueness
The property choices are what make a material both readable and unique. Carbon composes the display name and code from the taxonomy rather than from free text: substance, form, and where set the finish, grade, and dimension names build the readable name, while the substance, form, and type codes build the material code. Two materials that resolve to the same combination of properties are the same material, which is the point of driving identity from the tables instead of a typed string.
Because dimensions belong to forms and grades and finishes belong to substances, a valid material is always an internally consistent set of choices. You cannot pick a bar dimension for a sheet, or a stainless grade for aluminum, because those lists are filtered to the parent you already chose.