Wednesday, June 24, 2026
16 changes · saas-19.2
New functionality added to Odoo
This update allows Odoo to automatically receive vendor bills from the Hungarian tax authority (NAV) via their API. Users can now sync bills from a specific time period or upload NAV XML files directly, streamlining invoice processing and ensuring accurate record-keeping in Hungary. This improves efficiency and compliance with Hungarian tax regulations.
Original PR description
In Hungary, we are already sending invoices to NAV(hungarian tax authority). Now this module `l10n_hu_edi_receive` provides functionality of receiving vendor bills through its API and also adds XML…
In Hungary, we are already sending invoices to NAV(hungarian tax authority). Now this module `l10n_hu_edi_receive` provides functionality of receiving vendor bills through its API and also adds XML importer for Hungarian localization. A button `Sync with NAV` is added in vendor bills list view, which opens a wizard where you can select time period and all bills uploaded between that time are fetched from NAV's API. Also directly uploading XML is also supported in the format NAV supports. Flow:- - After selection of time range, we call `queryInvoiceDigest` endpoint with that range and it returns a list of digests(consider each digest as separate invoice) but this digests contains only meta data not all details. - Now for each digest we call `queryInvoiceData` endpoint which returns xml response with `QueryInvoiceDataResponse` as root node. This xml contains some meta data and `InvoiceData` node which has a base64 string, when we decode that string we get an xml with `InvoiceData` as root node and this xml contains all details of the invoice, we parse both these details and create bills and refunds. Also xml importer supports any of the `InvoiceData` or `QueryInvoiceDataResponse` xml. NAV Documentation: https://onlineszamla.nav.gov.hu/files/container/download/2025.10.09.%20EN_Online%20Invoice%20System%203.0%20Interface%20Specification%20.pdf task-5237910 Forward-Port-Of: odoo/odoo#240919
Resolved issues and error corrections
This update resolves an issue preventing users from correctly processing credit notes in Croatia using the P10 process type. The fix adjusts internal rules to align with Croatian tax authority specifications, now permitting P10 for credit notes while maintaining the previous restriction. This ensures accurate e-invoicing compliance.
Original PR description
**Steps to reproduce:** * Install the Croatian e-invoicing module (**l10n_hr_edi**). * Set up a Croatian company and a Croatian customer partner (with OIB and VAT configured). * Create a customer…
**Steps to reproduce:** * Install the Croatian e-invoicing module (**l10n_hr_edi**). * Set up a Croatian company and a Croatian customer partner (with OIB and VAT configured). * Create a customer invoice and post it. * Create a credit note from that invoice via **Credit Note** button. * On the credit note, change **Business Process Type** from `P9` to `P10: Issuing a corrective invoice`. * Save the credit note. **Observed behavior:** * Saving fails with: "Business Process Type P9 can only be used with credit notes and vice versa." * The error occurs even though P10 is a valid process type for credit notes according to the Croatian tax authority specification. **Cause:** * The `_check_l10n_hr_process_type` constraint used an XOR-style boolean check: `(process_type == 'P9') == (move_type != 'out_refund')`. * This enforced an exclusive P9 ↔ out_refund mapping, making P9 the **only** allowed process type for credit notes and blocking P10 entirely. * Per the Croatian tax authority specification, P10 (corrective invoice) is explicitly valid for credit notes: full cancellations report negative quantities/amounts, and partial corrections may report either positive or negative values. **Fix:** * Replace the XOR constraint with two independent, clearly-scoped rules: - P9 may only be used on credit notes (`out_refund`). - Credit notes must use either P9 or P10. * Add P10 UBL type code mapping in `_ubl_add_credit_note_type_code_node()`: P10 credit notes now emit `CreditNoteTypeCode 384` (Corrected Invoice, UNTDID 1001) instead of falling through to the default 381. opw-6128955 - Official Croatian Information Intermediary: https://portal.moj-eracun.hr/blog/kako-stornirati-eracun/ - Croatian Tax Authority (FAQ on fiscalization and e-invoicing): https://porezna-uprava.gov.hr/UserDocsImages/Fiskalizacija/Fiskalizacija_eRacun/Pitanja%20i%20odgovori%20vezani%20uz%20Zakon%20o%20fiskalizaciji.pdf Forward-Port-Of: odoo/odoo#266288
This update fixes a previous issue where commission calculations were incorrectly applying to employees on long-term sick leave. The change ensures that employees on partial incapacity or long-term sickness are not subject to commission deductions, aligning with proper accounting and payroll regulations. This ensures accurate commission payments for employees in extended periods of absence.
Original PR description
Partial incapacity and long term sickness are not elligible to loss on commissions.
This update resolves an issue where E-Way Bill amounts were incorrectly calculated when sales prices included tax. The fix ensures that tax is handled accurately, displaying the correct taxable and total amounts in the generated E-Way Bills. This ensures compliance with Indian tax regulations.
Original PR description
`*` = `ewaybill_Stock, sale_stock, purchase_stock` **Steps to reproduce:** * Install `l10n_in_ewabill_stock` and `l10n_in_sale_Stock`. * Set the Default Tax Price Setting to "Tax Included". * Create…
`*` = `ewaybill_Stock, sale_stock, purchase_stock` **Steps to reproduce:** * Install `l10n_in_ewabill_stock` and `l10n_in_sale_Stock`. * Set the Default Tax Price Setting to "Tax Included". * Create a Sales Order (e.g. unit price 300, qty 600, 18% GST) and confirm the Delivery Challan/Delivery Order. * Generate an E-Way Bill from the Delivery Challan. **Observed behavior:** * The Taxable Amount and Total Invoice Amount are displayed incorrectly in the generated E-Way Bill, including both the printed document and the JSON. * The `ewaybill_price_unit` shows the tax-excluded price (e.g. 254.24) instead of the original tax-included price (300), leading to a double tax exclusion when `compute_all` processes it. **Cause:** * `_l10n_in_get_product_price_unit` in both `l10n_in_sale_stock` and `l10n_in_purchase_stock` unconditionally used `price_subtotal / qty` to compute the E-Way Bill price unit. `price_subtotal` is always tax-excluded, so for tax-included prices, the tax was already stripped. * `_l10n_in_tax_details_by_stock_move` then passed this already tax-excluded price to `compute_all` with taxes that have `price_include=True`, causing `compute_all` to strip the tax a second time (e.g. 254.24 / 1.18 = 215.46 instead of the correct 254.24). **Fix:** * Check whether any of the line's taxes have `price_include` set. If so, use `price_total / qty` (which preserves the tax-included price) so that `compute_all` can correctly extract the tax. Otherwise, continue using `price_subtotal / qty` as before. opw-6273101 Forward-Port-Of: odoo/odoo#271492 Forward-Port-Of: odoo/odoo#268504
This update resolves an issue where Odoo couldn't correctly retrieve lot numbers from GS1 barcodes containing leading zeros (like '10'). The fix ensures accurate lot number identification when scanning these barcodes, preventing errors and improving inventory management. It corrects a parsing problem within the barcode scanning process.
Original PR description
When we scan a gs1 barcode that has in his lot a special character and start with 10, odoo will not be able to retrieve it from the db. **Steps to reproduce** * In settings activate: - Lots & Serial…
When we scan a gs1 barcode that has in his lot a special character and start with 10, odoo will not be able to retrieve it from the db. **Steps to reproduce** * In settings activate: - Lots & Serial Numbers - Units of Measure & Packagings - Storage Locations - Barcode Scanner : GS1 nomenclature * Create a Product tracked by lot with - barcode: 00001234567895 * Add on hand quantity: - 100 kg in lot : 10002002303-4 - 100 kg in lot : 11002002303-4 * Go to barcode>Operation>Internal Transfer>New * Scan 02000012345678951010002002303-4#3100000100 meaning: - 02 following 14 characters are the product barcode - 10 following characters are the lot number - "#" separator - 3100: means the units are kilograms, - 00100 means 100 units. -> if you check with the edit button the lot was not found (if you click on validate it will trigger an UserError for missing lot) **Observation** When scanning the GS1 barcode it will call onBarcodeSubmitted->onBarcodeScanned where we will execute processBarcode: https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/stock_barcode/static/src/components/main.js#L387 Where we will deconstruct the barcode into his component en retrieve from the db the relevant data: https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/stock_barcode/static/src/models/barcode_model.js#L709-L717 - First the barcode is parsed, identifiers are erased and each section is separated, the variable with our lot number only has the lot number in it, the identifier (10) is not included, BarcodeObject.forBarcode(bc) -> new BarcodeObject -> parser.parse_barcode: https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/stock_barcode/static/src/barcode_object.js#L14 - Check if the data is in the cache, if not, set it to retrieve after - Retrieve missing data getMissingRecords : https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/stock_barcode/static/src/lazy_barcode_cache.js#L349 From here we will get a call to get_specific_barcode_data for each element: https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/stock_barcode/controllers/stock_barcode.py#L176 In the case of the stock.lot since it has a symbol and it's not only digit it will skip the gs1 nomenclature domain converter (it will not become 'ilike' and stay with 'in'): https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/stock_barcode/controllers/stock_barcode.py#L182-L197 We will do the search: https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/stock_barcode/controllers/stock_barcode.py#L205 during which we will retrieve specific query from the stock.lot module : https://github.com/odoo/odoo/blob/8d14665af5acf1bd391d05a5048dc701986e8b15/odoo/orm/models.py#L1408 Where, since it's a GS1 nomenclature, we will preprocess the agrs: https://github.com/odoo/enterprise/blob/8030b105d3fce1eef9b8965a2bfc37195f71723c/stock_barcode/models/stock_lot.py#L14 -> Since our barcode start with a 10, it will erase it, which lead to a miss in the search. It will also avoid further searches since we avoid multiple search on the same elements (added in missingBarcodeKeyCache in getMissingRecords). https://github.com/odoo/enterprise/blob/c6d18a7a92092ffdf96f4569a70e95bdc276441c/stock_barcode/static/src/lazy_barcode_cache.js#L294-L298 opw-6207120 Forward-Port-Of: odoo/enterprise#118828
This update resolves a problem where Point of Sale order sequences generated with dynamic prefixes (like years) weren't correctly formatted. The fix ensures that sequence numbers are properly generated and updated, preventing errors in order creation and payment processing. This improves the reliability of the POS system.
Original PR description
**Steps to reproduce:** - Create a database in version 19.0 and install Point of Sale. - Create a sequence with a prefix or suffix using placeholders like %(year)s. - Assign this sequence to a POS…
**Steps to reproduce:** - Create a database in version 19.0 and install Point of Sale. - Create a sequence with a prefix or suffix using placeholders like %(year)s. - Assign this sequence to a POS configuration. - Create a new POS order and confirm payment. **Issue:** - POS order `sequence_number` must be an integer, but when using dynamic prefixes/suffixes (e.g., %(year)s), `_next()` returns values like `POS/2026/` while the configured prefix remains `%(year)s`. - Due to this mismatch, [`_update_sequence_number`](https://github.com/odoo/odoo/blob/ab6cfabf0086afced2d035eb2207a0acab655540/addons/point_of_sale/models/pos_order.py#L561) fails to correctly remove the prefix/suffix. - The root cause is that placeholders such as `%(year)s` are not interpolated before applying prefix/suffix removal logic, causing string mismatch and failure in extracting the numeric part.<img width="1920" height="959" alt="image" src="https://github.com/user-attachments/assets/d331fb7a-3c0f-4e34-a33e-6ec906be77bb" /> **Solution:** - Interpolate prefix and suffix before removing them from the generated sequence. - Convert placeholders like %(year)s into actual values (e.g., 2026). - Then apply prefix/suffix removal logic. opw-6150204 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#262427
This update clarifies Redsys payment errors by mapping their technical codes to understandable messages. Previously, errors were difficult to diagnose, making it hard to resolve payment issues and provide accurate information to customers. This change improves the reliability and transparency of Redsys payments within Odoo.
Original PR description
Raw Redsys response codes were not human-readable, making it hard to diagnose failed transactions or provide meaningful feedback. See: https://pagosonline.redsys.es/desarrolladores-inicio/integrate-con-nosotros/parametros-de-entrada-y-salida/#tablepress-11_wrapper --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#269968
This update resolves an issue where delivery dates on sales orders weren't correctly applied to manufacturing orders, leading to scheduling conflicts. The fix ensures that delivery dates are consistently propagated to finished moves, preventing mismatched deadlines and enabling accurate production planning. This improves the reliability of the manufacturing process.
Original PR description
Steps to reproduce: - Create a storable product with a BoM, routes MTO + Manufacturing, costing method FIFO + Perpetual (at invoicing) - Confirm a sale order for 1 unit → a manufacturing order is…
Steps to reproduce: - Create a storable product with a BoM, routes MTO + Manufacturing, costing method FIFO + Perpetual (at invoicing) - Confirm a sale order for 1 unit → a manufacturing order is created - Set a Delivery Date on the SO (Other Info tab) - Increase the SO line qty to 2 - Validate the MO → traceback on finished_move.ensure_one() Problem: When a delivery date is set on the SO, it propagates to the MO's finished move via date_deadline. However, `production.date_deadline\ was not updated (guarded by `if not production.date_deadline`) because the MO already had a deadline set at planning time: https://github.com/odoo/odoo/blob/19.0/addons/mrp/models/mrp_production.py#L476 When the SO qty then increases, change_production_qty copies the finished move to create a delta move. That delta move receives production.date_deadline (the stale planning date) instead of the delivery date, so the two finished moves end up with different deadlines and cannot be merged: https://github.com/odoo/odoo/blob/19.0/addons/mrp/wizard/change_production_qty.py#L43 Solution - always update production.date_deadline from its finished moves - link the new delivery move to the finished move after the qty wizard runs so it gets reserved after MO validation opw-6273076 Forward-Port-Of: odoo/odoo#269405
This change resolves an issue where the 'Add Property' button disappeared after navigating between worksheet templates. The fix ensures the button remains visible and functional after using the navigation controls, improving usability for users working with complex data structures. The underlying problem was a misconfiguration of edit mode state during record navigation.
Original PR description
Steps to reproduce: ------------------------------------------------- 1. Install `planning_field_service_worksheet` module with demo data 2. Go to Worksheet Templates 3. Open First Worksheet >…
Steps to reproduce:
-------------------------------------------------
1. Install `planning_field_service_worksheet` module with demo data
2. Go to Worksheet Templates
3. Open First Worksheet > Observe `+ Add Property` button at bottom
4. From the Navigation button, move to the next Worksheet Template
5. Come back to First Template using the same navigation button
Observation:
-------------------------------------------------
The '+ Add Property' button and property edit buttons disappear after navigating away from and back to the first worksheet template.
Issue:
-------------------------------------------------
`PropertiesDefinitionField.setup()` sets
`this.state.isInEditMode = this.definitionRecordId` only once during component initialization.
https://github.com/odoo/odoo/blob/8cf75467969eb423b8e50e8be45ebf5f60f1cc43/addons/web/static/src/views/fields/properties/properties_definition_field.js#L9-L12
When the user navigates via the pager, `FormController.onWillLoadRoot` resets `propertiesState.editable` to `false` and fires a `PROPERTY_FIELD:EDIT` bus event with `{ editable: false }`, which calls `setEditMode(false)` https://github.com/odoo/odoo/blob/8cf75467969eb423b8e50e8be45ebf5f60f1cc43/addons/web/static/src/views/form/form_controller.js#L407
After the new record loads, the parent's `useEffect` (which watches the definition record field) should restore edit mode, but it short-circuits when both `isInEditMode` and `editMode` are `false`
https://github.com/odoo/odoo/blob/8cf75467969eb423b8e50e8be45ebf5f60f1cc43/addons/web/static/src/views/fields/properties/properties_field.js#L115-L117
Since `setup()` doesn't re-run on record navigation and nothing else restores `isInEditMode`, it stays `false` permanently. This hides the parent template's 'Add Property' button
https://github.com/odoo/odoo/blob/8cf75467969eb423b8e50e8be45ebf5f60f1cc43/addons/web/static/src/views/fields/properties/properties_field.xml#L86-L90
Solution:
-------------------------------------------------
* Replace the one-time assignment in `setup()` with a `useRecordObserver` that sets `this.state.isInEditMode` whenever the record changes. This hook fires both on initial setup (via `onWillStart`) and on every record change (via `onWillUpdateProps`) ensuring `isInEditMode` is correctly restored after pager navigation
* Using `record.data.id` rather than `true` preserves the existing behavior of disabling edit mode for unsaved records (where `id` is `false/falsy`)
opw-6264361This update fixes an issue where product pricing in the Point of Sale (PoS) system was incorrectly calculating VAT and total prices. The fix ensures that prices, including VAT, accurately reflect the configured pricelist and fiscal position mappings, leading to more reliable sales calculations. This improves the accuracy of transactions and reporting.
Original PR description
Steps to reproduce: ------------------- 1. Create a product with a tax (e.g. 15%). 2. Create a pricelist that changes the price (e.g. 100 to 200). 3. Create a fiscal position mapping the tax (e.g.…
Steps to reproduce: ------------------- 1. Create a product with a tax (e.g. 15%). 2. Create a pricelist that changes the price (e.g. 100 to 200). 3. Create a fiscal position mapping the tax (e.g. 15% to 30%). 4. Add the pricelist and the fiscal position in PoS. 5. Add the product to the cart, and select the tax and the pricelist created in the previous steps. 6. Long press on the product to see its info. The price should be 200 now after selecting the pricelist. Also the tax should be 30% bc of the FP mapping, i.e. total price should be 200 + 30% = 260. However, we observe that VAT shows 15 (15%) instead of 60 (30%), and Price incl. Tax shows 230 instead of 260. What's happening: ----------------- On the frontend, `getTaxDetails()` is called with no options, so it uses the product `list_price` (100) and `taxes_id` (15%), giving VAT = 15. Alos, on the backned, `self.taxes_id` is used directly to compute the taxes, even though the pricelist price is correct (200), fiscal position is ignored, hence 200 + 15% = 230 instead of 200 + 30% = 260. The fix: -------- On frontend, we pass the pricelist and fiscal position to `getTaxDetails`, and compute the tax name from the mapped taxes. On the backend, we read the `fiscal_position_id` from the context and apply the tax mapping, so the correct taxes are used. opw-6200632 Forward-Port-Of: odoo/odoo#266012
This update fixes a scheduling issue in manufacturing order planning where operations with dependencies were not always processed in the correct order. The change ensures that operations are planned based on their dependencies, preventing delays and improving production efficiency. It addresses a bug related to recursive planning that caused operations to be scheduled incorrectly.
Original PR description
Bug introduced in: https://github.com/odoo/odoo/commit/cfc5c998035b4268c36f5097782888e18e21b4fe Steps to reproduce the bug: - Create a product with a BoM with operation dependencies enabled - Add 4…
Bug introduced in: https://github.com/odoo/odoo/commit/cfc5c998035b4268c36f5097782888e18e21b4fe
Steps to reproduce the bug:
- Create a product with a BoM with operation dependencies enabled
- Add 4 operations on the same workcenter:
- opA: no blocker
- opB: blocked by opA
- opC: blocked by opA
- opD: blocked by opC
- Confirm a manufacturing order from this BoM
- Click Plan
Problem:
opA was scheduled after opB, violating the dependency.
`_plan_workorders` starts planning from the "leaf" workorders (those with no dependents). Given the structure above, the initial set is [opB, opD]. Processing opB first correctly plans opA then opB. But processing opD triggers a recursive chain opD→opC→opA which calls `action_unplan(opA)` and replans it from scratch. By then, opB already occupies the workcenter slot that opA originally held, so opA ends up scheduled after opB.
Solution:
Add `and not wo.is_planned` to the filter on `blocked_by_workorder_ids` in the recursive call inside `_plan_workorders`. Workorders that are already planned are skipped instead of being unplanned and replanned, preserving the correct order.
opw-6299179This update resolves a problem where Italian fiscal printers would intermittently stop printing POS orders due to unsupported characters in product or payment method names. The fix replaces these characters with spaces, following official EPSON documentation to ensure proper printing functionality. This prevents incomplete order prints and improves the POS experience for Italian users.
Original PR description
Steps to reproduce: - Setup an Italian fiscal printer - Modify the name of a product to use the non-blocking space character "\ "; - In the POS, create an order with the product. Error: the fiscal device will stop midway in the printing process and return an incomplete response to the frontend. The issue can also be reproduce if the character is included in the payment method name or the POS config name. Solution: When formating the xml command, replace all non-supported character by a space character. The non-supported character list is provided by the official [EPSON fiscal printer documentation](https://support.epson.net/setupnavi/?PINF=bsmanual&OSC=WS&LG2=EN&MKN=FP-90III%20RT) in the document "ePOS Fiscal Print Solution Development Guide". Other: Rename the file "dispaly_text.xml" to "display_text.xml". [opw-6244089](https://www.odoo.com/odoo/project/49/tasks/6244089) Forward-Port-Of: odoo/enterprise#121248 Forward-Port-Of: odoo/enterprise#120169
This update fixes a performance issue within the Odoo gevent server by proactively loading database registries. Previously, the server wasn't properly configuring these registries, leading to slower startup times. This change ensures optimal performance and responsiveness of the Odoo SaaS platform.
Original PR description
The code to set the registry size was moved to `preload_registries`. The gevent server does not preload registries and thus does not set the registries size. Instead of moving the code again, we can preload registries in the gevent server. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#271640
This update ensures Odoo generates PDF invoices that fully comply with ZUGFeRD standards, a crucial requirement for accurate electronic invoicing. Specifically, the PDF now correctly identifies the relationship between the embedded XML data and the visual invoice, and the XML filename has been updated for compatibility with the latest ZUGFeRD version.
Original PR description
Adapt `add_attachment` to allow setting the "AFRelationship" tag on the PDF filespec object, In compliance with Factur-X/ZUGFeRD specs that require the AFRelationship tag in the PDF filespec object to reflect the relationship between the embedded XML and the visual PDF content: - /Data: the visual PDF contains more invoicing data than the XML. - /Alternative: the XML and the PDF are two equivalent representations of the same invoice. Additionally, update the embedded XML filename from `zugferd-invoice.xml` to `factur-x.xml`. The former is marked as deprecated since ZUGFeRD 2.0 Ref: sections 6.2.2, 6.3.1, 6.3.2 of the ZUGFeRD 2.4 specification: https://www.ferd-net.de/en/downloads/publications/details/zugferd-24-english opw-6252082 Forward-Port-Of: odoo/odoo#271406 Forward-Port-Of: odoo/odoo#269117
A technical issue preventing the creation of opportunity buttons on the website was resolved. The fix corrects a problem caused by recent code changes that incorrectly called a function for creating HTML elements. This ensures that users can now consistently access the opportunity creation feature.
Original PR description
**Steps to reproduce:** - Install CRM app with website_crm_partner_assign - Go to the current user contact page > Partner Assignment - Set its partner level (e.g. Gold) - Go the `/my/opportunities` url of the website - Create opportunity button should be available - On click an error is raised: `TypeError: this.el.createElement is not a function` **Issue:** `createElement` is a method of `Document`, but it's called from a dom element after some `Interactions` refactoring. **Fix:** Properly call the method like before. related: https://github.com/odoo/odoo/commit/22e777c046521f3f89b62caa5876680beb7f5aba opw-6267473 Forward-Port-Of: odoo/odoo#268036
This update resolves an issue where the due date calculation for French payroll was incorrect, specifically returning a month of 0 for November transactions. Additionally, a bug was fixed that prevented errors when processing empty recordsets, ensuring data integrity and reliable payroll processing. This improves the accuracy and stability of the French payroll module.
Original PR description
- Fix due date calculation (returned month 0 if move date was in November) - Fix ensure_one error, avoid calling _deduce_country_code() on an empty recordset opw-6293701 Forward-Port-Of: odoo/odoo#270148 Forward-Port-Of: odoo/odoo#270003