# Excel Image Paste Feature for Master Order Lines ## Overview This feature enables users to copy images from Excel and paste them directly into Master Order line image cells using `Ctrl+V`, providing an Excel-like workflow for product image management. --- ## Feature Summary | Aspect | Details | |--------|---------| | **Target Users** | Purchasing and sourcing teams | | **Use Case** | Paste product images from Excel directly into order lines | | **Supported Formats** | PNG, JPEG, BMP, GIF, WEBP | | **Max Image Size** | Auto-resizes if > 1024x1024 pixels | | **Storage** | Odoo filestore via `ir.attachment` | | **Browser Support** | Chrome, Edge, Firefox (desktop) | --- ## User Flow ``` 1. Open Master Order form 2. Click on image cell in Order Lines (cell gets focus ring) 3. Copy image from Excel (Ctrl+C on image) 4. Click target image cell 5. Press Ctrl+V 6. Image appears instantly with success notification 7. Save the form to persist ``` --- ## Technical Architecture ### Files Structure ``` at_master_order/ ├── static/src/ │ ├── js/ │ │ ├── image_paste_widget.js # Main OWL widget │ │ └── master_order_form.js # Form controller enhancements │ ├── xml/ │ │ └── image_paste_templates.xml # OWL templates │ └── scss/ │ └── image_paste.scss # Styling for paste interaction ├── models/ │ └── master_order_line.py # Backend image processing └── views/ └── master_order_views.xml # View with widget="image_paste" ``` ### Component Diagram ``` ┌─────────────────────────────────────────────────────────────┐ │ Browser (Frontend) │ ├─────────────────────────────────────────────────────────────┤ │ ┌─────────────────────┐ ┌─────────────────────────────┐ │ │ │ ImagePasteField │ │ Clipboard API │ │ │ │ (OWL Widget) │◄───│ (DataTransfer/Clipboard) │ │ │ │ │ └─────────────────────────────┘ │ │ │ - onPaste() │ │ │ │ - _processImage() │ ┌─────────────────────────────┐ │ │ │ - _saveToRecord() │───►│ Canvas API │ │ │ └─────────────────────┘ │ (Image resize) │ │ │ │ └─────────────────────────────┘ │ │ ▼ │ │ ┌─────────────────────┐ │ │ │ record.update() │ │ │ │ (OWL Record) │ │ │ └─────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Server (Backend) │ ├─────────────────────────────────────────────────────────────┤ │ ┌─────────────────────┐ ┌─────────────────────────────┐ │ │ │ master_order_line │───►│ ir.attachment │ │ │ │ - image_1920 field │ │ (Image storage) │ │ │ │ - write() override │ └─────────────────────────────┘ │ │ │ - _process_image() │ │ │ └─────────────────────┘ ┌─────────────────────────────┐ │ │ │ Filestore │ │ │ │ (Binary data) │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Key Implementation Details ### 1. OWL Widget (`image_paste_widget.js`) ```javascript // Extends Odoo's ImageField export class ImagePasteField extends ImageField { static template = "at_master_order.ImagePasteField"; setup() { super.setup(); // CRITICAL: Extend state, don't replace it! this.state.isFocused = false; this.state.isProcessing = false; } async onPaste(ev) { // 1. Get image from clipboard // 2. Process/resize if needed // 3. Save via record.update() } } ``` ### 2. Template (`image_paste_templates.xml`) ```xml
``` ### 3. View Configuration (`master_order_views.xml`) ```xml ``` ### 4. Model Field (`master_order_line.py`) ```python image_1920 = fields.Image('Product Image', attachment=True, help='Upload product image for this line. Supports paste from Excel (Ctrl+V).') ``` --- ## Critical Lessons Learned ### 1. OWL State Inheritance **Problem**: Overwriting `this.state` in child widget destroyed parent's `isValid` property. ```javascript // WRONG - destroys parent state this.state = useState({ isFocused: false }); // CORRECT - extends parent state this.state.isFocused = false; ``` ### 2. ImageField URL Generation The `getUrl()` method checks `!this.state.isValid` and returns placeholder if falsy. Parent's state must be preserved. ### 3. Attachment Storage Image fields with `attachment=True` store data in `ir_attachment` table, not as a database column. Use ORM methods to access, not direct SQL. ### 4. Cache Invalidation ImageField uses `write_date` for cache busting. Ensure `fieldDependencies` includes `write_date`. --- ## Database Schema ### ir_attachment (Image Storage) | Column | Value | |--------|-------| | res_model | `at.master.order.line` | | res_field | `image_1920` | | res_id | Line ID | | type | `binary` | | store_fname | Filestore path | | mimetype | `image/jpeg`, `image/png`, etc. | ### Query to Check Images ```sql SELECT l.id, l.name, a.id as attachment_id, a.file_size FROM at_master_order_line l LEFT JOIN ir_attachment a ON a.res_model = 'at.master.order.line' AND a.res_field = 'image_1920' AND a.res_id = l.id; ``` --- ## Image URL Format ``` /web/image/at.master.order.line/{line_id}/image_1920?unique={write_date} ``` --- ## Troubleshooting | Issue | Cause | Solution | |-------|-------|----------| | Placeholder shown instead of image | `state.isValid` is undefined | Don't overwrite parent's `this.state` | | Image not saving | Clipboard API not returning data | Check browser console, ensure HTTPS | | 404 on image URL | Attachment not linked correctly | Check `ir_attachment` table | | No paste event firing | Cell not focused | Click cell first to focus | --- ## Testing Checklist - [ ] Paste PNG image from Excel - [ ] Paste JPEG image from Excel - [ ] Paste large image (> 1024px) - should auto-resize - [ ] Save form after paste - [ ] Reload page - image should persist - [ ] Delete image (Delete key when focused) - [ ] Multiple pastes on different rows - [ ] Paste on new unsaved line --- ## Dependencies ### Frontend - `@web/views/fields/image/image_field` (ImageField) - `@web/core/utils/hooks` (useService) - `@odoo/owl` (Component, useState, onMounted, useRef) ### Backend - `PIL/Pillow` (Image processing) - `base64` (Encoding) --- ## Version History | Version | Date | Changes | |---------|------|---------| | 1.0 | 2026-01-12 | Initial implementation | | 1.1 | 2026-01-12 | Fixed state inheritance bug | | 1.2 | 2026-01-12 | Fixed template to use getUrl() |