# 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() |