Skip to content

[6791][IMP] product_chemical - #6

Open
kanda999 wants to merge 25 commits into
18.0from
18.0-add-product_chemical
Open

kanda999 wants to merge 25 commits into
18.0from
18.0-add-product_chemical

Conversation

@kanda999

@kanda999 kanda999 commented Jun 3, 2026 •

Copy link
Copy Markdown

kanda999 and others added 2 commits June 3, 2026 03:16
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kanda999

Copy link
Copy Markdown
Author

Pushed 2fb552b to 18.0-add-product_chemical (fixup, to be squashed).

Chemical products are managed in different units of measure (in the customer's data: 198 in volume units, 47 in weight units and 204 in "Units"), so the component amount column was summing mL, μL and pieces into a single meaningless total. This commit makes the report unit-aware without introducing any conversion:

  • Show the unit. Added product_uom_id (the product's stock UoM) to product.chemical.location.amount, exposed via pt.uom_id in _table_query and added to the list view, to the "Component Amount by Location" list on the product form and to the "Chemicals" list on the location form. uom_id was added to _depends so the report follows a UoM change.
  • Drop the misleading grand total. Removed sum="Total" from component_amount in the three list views.
  • Group by unit. Added a "Unit of Measure" group-by filter and made the action's default grouping Location → Substance → Unit of Measure (the numeric values of search_default_group_* drive the group-by order), so that the innermost subtotals are unit-homogeneous.
  • Added the ja translation and documented the behaviour in readme/DESCRIPTION.md (README artifacts regenerated).
  • Also fixed the pre-commit failures that were already on this branch (prettier, ruff-format, and W8113 attribute-string-redundant on component_amount). Note that this reformats a few lines in product_template_views.xml that are unrelated to this change.

Known limitation: Odoo renders group subtotals for numeric fields regardless of the sum attribute, so intermediate group rows (e.g. Location × Substance) can still show a mixed-unit subtotal. Removing those as well would require aggregator=None on quantity / component_amount, which would also remove the unit-homogeneous subtotals; converting to a per-substance reporting unit is being discussed with the customer as the follow-up.

 product_chemical/README.rst                                     | 28 ++++++++++++++++++---
 product_chemical/i18n/ja.po                                     |  6 +++++
 product_chemical/models/product_chemical_location_amount.py     | 11 +++++----
 product_chemical/readme/DESCRIPTION.md                          |  6 ++++-
 product_chemical/static/description/index.html                  | 28 ++++++++++++++++++---
 product_chemical/views/product_chemical_location_amount_views.xml | 10 ++++++--
 product_chemical/views/product_template_views.xml               | 19 ++++-----------
 product_chemical/views/stock_location_views.xml                  |  3 ++-

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kanda999

Copy link
Copy Markdown
Author

Pushed dfc3de1 to 18.0-add-product_chemical (fixup, to be squashed).

Formatting only, no behaviour change: ran pre-commit over the whole module, which reformatted product_chemical_law_views.xml (prettier) and product_chemical_law_line.py / product_chemical_minor_category.py / product_template.py (ruff-format). The previous fixup only covered the files it touched, so these were still failing CI. The pre-commit check is green now.

On-hand amounts alone cannot answer how much of a substance was handled over a
period, which is how the reporting obligations are expressed. Validating a
stock move of a chemical product now records the amount of each contained
substance, one record per substance, signed positive for a receipt into an
internal location and negative for a delivery out of one. A move between two
internal locations is recorded as an internal transfer and left out of the
totals, and a move that never touches an internal location is not recorded at
all, since nothing is handled on site.

The amounts are snapshots, precomputed when the record is created, so that
revising the composition of a product does not rewrite what was handled in the
past. Inventory managers can correct a record from its form view, and rebuild
the records of a move to cover the moves validated before the module was
installed, or a composition that was registered wrongly.

Component amounts are also converted into the chemical aggregation unit of the
product's unit of measure category, so that amounts of the same substance held
in products measured in different units add up into a single total.
The names described where the data came from rather than what it is,
which made the two reports hard to tell apart, and the product lines did
not carry the prefix of the model they hang off:

  product.chemical.location.amount -> product.chemical.stock
  product.chemical.move.amount     -> product.chemical.consumption
  product.chemical.law.line        -> product.template.chemical.law.line
  product.chemical.substance.line  -> product.template.chemical.substance.line
  product.chemical.major.category  -> product.chemical.law.major.category
  product.chemical.minor.category  -> product.chemical.law.minor.category

The migration scripts go with them: 18.0.1.0.0 is what is released on
18.0 and it carries the is_chemical flag alone, so none of the renamed
models ever reached a database that will run this upgrade.

Split the access rights along the way. The masters are reference data
every user reads and an inventory manager maintains, and the product
lines belong to a product manager, so read and write no longer come
together for everyone.

Drop the mrp dependency, which nothing used, along with the law product
count and the chemical amount list on the location form, which no view
showed any more.
…ated

One2many defaults to copy=False, while is_chemical and
track_chemical_consumption are booleans and are copied regardless.
Duplicating a chemical product therefore gave a product that still
presented itself as a tracked chemical but held no substance at all:
absent from the on hand report, recording no consumption, and with
nothing on the form to say why.

Cover product.chemical.stock along the way. Its whole query lives in one
_table_query string that no reviewer re-reads, so each test pins one
clause of it: the conversion into the aggregation unit, the fallback for
a category that has none, the internal location filter, and the grouping
that sums a product's variants into a single row.
The module carried demo data at one point and does not any more, but
ja.po was exported from a database that still held those records. 71 of
its 163 entries -- laws, categories, substances and products such as
"Fire Service Act" or "2-Propanol (Reagent Grade)" -- referenced xmlids
that the module no longer defines.

None of the 71 had a second, live occurrence, so the entries go whole
rather than losing a reference line. What remains points only at fields,
views, models, constraints, menus and actions that views/ still defines,
and every one of the 92 messages left is translated.
The description named an "Update Chemical Amounts" action that exists
under neither of its two labels: the stock move list and form offer
"Update Chemical Consumption", and the Chemical Consumption list offers
"Update from Stock Move". Name both where they appear instead.

The CAS master became product.chemical.substance and its menu became
"Chemical Substances", so the wording around it follows.

Drop the "(PDF)" from the risk assessment sheet as well.
risk_assessment_file is a plain binary field that accepts any file type,
so the parenthesis promised a restriction the module does not enforce.

Regenerate README.rst and static/description/index.html.
…e master

A content rate belongs to the product that holds the substance, not to
the substance itself: the same CAS number sits at a different rate in
every product it appears in. The field only ever seeded a newly added
product line through an onchange, and a master column that is right for
one product and wrong for the next misleads more than it helps.

The field goes with everything that hung off it: the CHECK constraint
that guarded its range, the two substance views that showed it, the
onchange that read it, and the three translation references. The rate is
entered on the product line, which is where it is stored anyway.
unique(move_id, substance_id) already builds a btree whose leading
column is move_id, so a lookup by move -- which is what reading the
chemical consumption of a move does -- uses it. And company_id is
neither filtered nor sorted on today; the index can come back with the
multi-company record rule that would give it something to do.

Removing index=True does not drop an index that already exists: the
registry keeps it and logs "Keep unexpected index". Only a database
created from here on will be without them.

The comment on product.template.write() goes as well. Clearing
track_chemical_consumption along with is_chemical reads clearly enough
from the two lines that do it.
A consumption record was half snapshot and half live view of its move: the
quantity, the units and the content rate were frozen at creation, while the
product, the date and the two locations were stored related fields that
followed whatever the move was later made to say. A move corrected after the
fact therefore moved a reported amount into another period, or left it
showing the locations of a return while still counting as a consumption.

Precompute the four of them from the move as well, so that the whole record
describes the move as it was validated, and freeze what is left: move_id and
substance_id become readonly, and the content rate loses the writable flag
that let an inventory manager type over it.

With nothing left to edit, the form view goes, the list refuses create, edit
and delete, and the manager loses write on the model -- create and unlink
stay, the rebuild action needs them. A composition that was registered
wrongly is fixed on the product and replayed with Update Chemical
Consumption, which is the only correction path there was any point in
keeping: it rebuilds the whole move, so the figure always traces back to a
move and a composition rather than to something someone typed over it.

The CHECK on the content rate goes with the writable flag. The value only
ever arrives from the product line, which carries the same constraint.

Drop the two translations that no longer have a term behind them: the help
of the locations, which was inherited from stock.move and described an
operation rather than a record, and the company of a field removed earlier.
Drop the Smart Search entries as well -- base_name_search_improved injects
that field into every model, and this one does not depend on it.
All four masters carry an active flag, but only the laws and the substances
offered the Archived filter in their search view. A major or minor category
that was archived therefore dropped out of its own menu with no way to bring
it back on screen, and so no way to unarchive it.
required=True on a Float promises that a content rate has to be entered, but
0.0 is a value like any other: the client considers the field filled and the
line saves at 0%, silently reporting the substance with no amount at all. The
flag only ever made the column NOT NULL, which the default already
guaranteed. The range is still guarded by the CHECK constraint.
The module is still unreleased on this branch and goes out as a single
addition, so it carries the initial version rather than the running count of
the commits that built it.
@nobuQuartile
nobuQuartile marked this pull request as ready for review September 2, 2026 01:27
@nobuQuartile

Copy link
Copy Markdown
Contributor

@AungKoKoLin1997
Could you review this PR?

Comment thread product_chemical/readme/CONTRIBUTORS.md
Comment thread product_chemical/models/product_chemical_consumption.py
Comment thread product_chemical/models/product_chemical_substance.py Outdated
Comment thread product_chemical/models/product_chemical_substance.py Outdated
Adding a name to the list is not a reason to take the previous one off it.
Both halves of the name are required, so guarding the CAS number on the name
as well only spelled out a case a saved record cannot be in, and the fallback
had two branches for a field that is never empty either.
…y CAS

The search box on the product view carried a filter_domain that went straight
at the CAS number of the product lines, which meant the search method behind
chemical_substance_ids was never reached: searching a substance by its name
returned nothing, only its CAS number matched.

Drop the override and let the field search itself. The substance master
already declares _rec_names_search on both its name and its CAS number, so
the box now matches either -- 'Ethanol' finds what '64-17-5' finds, and more,
where several substances share the name. The label says so.
Nine stored fields, six of which did nothing but hold a copy of a value the
move already carried, and a compute method each to put it there. The record
was frozen whole so that a move corrected after the fact could not rewrite an
amount that had already been reported -- but a done move is not rewritten in
practice, Odoo refuses to move its locations at all, and the rebuild action
re-reads everything from the move anyway, so the guarantee was paid for in
code and bought little.

Make the six related and drop their computes. They are not stored: ordering
by the actual date, grouping by the product, the two locations and the date,
and the date range filter all work through the join Odoo generates for a
related field, so the columns earn nothing either.

The composition stays put, which is the part worth freezing: the content rate
is still precomputed at creation and does not follow a revision of the
product, and a rate registered wrongly is replayed with Update Chemical
Consumption. The amount follows the move again through the rate, which is
what correcting the quantity of a move is meant to do.

The guard on the two units in _compute_amount goes as well. stock.move
requires its unit of measure and product.template requires its own, so
neither can be empty by the time the amount is computed.
@nobuQuartile

Copy link
Copy Markdown
Contributor

Ready to review

Comment thread product_chemical/models/product_chemical_consumption.py Outdated
Comment thread product_chemical/__manifest__.py Outdated
…bout

Sitting above move_id, the comment read as a note on the relation, which is
the one thing on this model that needs no explaining. Move it onto the
content rate, which is what it was always about, and say the one thing that
is not visible in the field definition: a product whose content rate is
revised must leave the rate the reported amounts were computed with alone.

The sentence on the move side goes. Six related fields say for themselves
that they follow the move.
stock depends on product, which depends on uom, so naming the three of them
only spelled out a chain Odoo already walks.
@nobuQuartile

Copy link
Copy Markdown
Contributor

Add ja.po

Translation notes

Translations are referenced from existing Odoo translations where available (odoo/stock, odoo/product, odoo/uom, odoo/account) — 26 of 90 entries.

Two entries intentionally deviate from their odoo/stock reference:

msgid ours odoo/stock reason
Returns 返却 返品 The filter is amount < 0, i.e. a substance returned to stock, not a product return/RMA.
Notes メモ ノート Core Odoo is split (account/purchase use メモ); keeping the already-committed value.

The following terms have no reference and are translated independently:

msgid msgstr
%(count)s chemical consumption record(s) created. %(count)s件の化学物質使用量を記録しました。
Actual Date 実績日
Aggregated Only 集計対象のみ
Amount UoM 成分量単位
Amount of the substance used up, negative when it is returned to the stock. 使用された化学物質の量。在庫へ戻した場合はマイナスになります。
Applicable Laws 適用法律
CAS No. CAS番号
CAS No. must be unique. CAS番号は一意である必要があります。
Categories カテゴリ
Chemical 化学物質
Chemical Aggregation UoM 化学物質集計単位
Chemical Consumption 化学物質使用量
Chemical Consumption Location 化学物質消費ロケーション
Chemical Law 化学物質法律
Chemical Law Major Category 化学物質法律大分類
Chemical Law Minor Category 化学物質法律小分類
Chemical Laws 化学物質法律
Chemical Substance 化学物質
Chemical Substances 化学物質マスタ
Chemical component amounts of products measured in this category are converted to this unit before being aggregated. Leave it empty to report the amounts without conversion, e.g. for count-managed products. このカテゴリの単位で管理されるプロダクトの成分量は、集計前にこの単位に換算されます。空欄にすると換算せずに成分量を表示します(個数管理品など)。
Chemicals by Location ロケーション別化学物質
Component Amount 成分量
Component Amount by Location ロケーション別成分量
Consumed Amount 使用量
Consumption 消費
Content Rate (%) 含有率 (%)
Content rate must be between 0 and 100. 含有率は0以上100以下である必要があります。
Date the move was processed. 在庫移動が処理された日時。
Each law can be added only once per product. 1プロダクトに対し各法律は1度のみ追加できます。
Each substance can be added only once per product. 1プロダクトに対し各化学物質は1度のみ追加できます。
Each substance can be recorded only once per stock move. 各化学物質は在庫移動ごとに1回のみ記録できます。
Law 法律
Law Name 法律名
Law name must be unique. 法律名は一意である必要があります。
Laws 法律
Major Categories 大分類
Major Category 大分類
Major category name must be unique per law. 大分類名は法律ごとに一意である必要があります。
Minor Categories 小分類
Minor Category 小分類
Minor category name must be unique per major category. 小分類名は大分類ごとに一意である必要があります。
Moved Qty 移動数量
On Hand Qty 手持在庫数
Only amounts converted into a chemical aggregation unit, i.e. excluding count-managed products. 化学物質集計単位に換算された成分量のみ(個数管理品を除く)。
Product Chemical Consumption プロダクト化学物質使用量
Product Chemical Law Line プロダクト化学物質法律明細
Product Chemical Stock プロダクト化学物質在庫
Product Chemical Substance Line プロダクト化学物質明細
Product UoM プロダクト単位
Returns 返却
Risk Assessment Sheet リスクアセスメント表
Risk Assessment Sheet Filename リスクアセスメント表ファイル名
Smart Search スマート検索
Substance 化学物質
Substance / CAS No. 化学物質 / CAS番号
Substance Name 化学物質名
Substances 化学物質
The chemical aggregation unit of measure of category %(category)s must belong to that category. カテゴリ%(category)sの化学物質集計単位は、そのカテゴリに属している必要があります。
Track Chemical Consumption 化学物質消費追跡
Tracked Chemical Consumption 化学物質消費追跡対象
Unit of measure the component amount is expressed in: the chemical aggregation unit of the UoM category, or the product unit when the category has none. 成分量を表す単位。単位カテゴリの化学物質集計単位、または集計単位が未設定の場合はプロダクトの単位。
Unit of measure the consumed amount is expressed in: the chemical aggregation unit of the UoM category, or the product unit when the category has none. 使用量を表す単位。単位カテゴリの化学物質集計単位、または集計単位が未設定の場合はプロダクトの単位。
Unit of measure the on-hand quantity is expressed in. 手持在庫数を表す単位。
Update Chemical Consumption 化学物質使用量を更新
Update from Stock Move 在庫移動から更新

Note on the export

The raw export also carried 71 Japanese names for product.chemical.substance / product.chemical.law* / product.template records. These were excluded: this module ships no data/ or demo/ files (and never has in its git history), so those records are orphan noupdate fixtures left in the dev database, not module content. This matches ad5a6b3 [FIX] product_chemical: drop the translations of removed demo data.

One obsolete entry was dropped (Unit of measure the moved quantity is expressed in. — the field's help text no longer exists in the source), and Smart Search was added.

@nobuQuartile
nobuQuartile force-pushed the 18.0-add-product_chemical branch from f6799a9 to c8eb4e0 Compare September 14, 2026 02:07
@AungKoKoLin1997

Copy link
Copy Markdown
Contributor

@nobuQuartile https://github.com/qrtl/claude-skills/pull/143/changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants