Tower: upload at_master_order 18.0.10.0 (via marketplace)
This commit is contained in:
254
addons/at_master_order/IMAGE_PASTE_FEATURE.md
Normal file
254
addons/at_master_order/IMAGE_PASTE_FEATURE.md
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# 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
|
||||||
|
<t t-name="at_master_order.ImagePasteField">
|
||||||
|
<div t-ref="imagePasteContainer" tabindex="0">
|
||||||
|
<!-- Uses parent's getUrl() method for image display -->
|
||||||
|
<img t-att-src="this.getUrl(props.previewImage || props.name)"/>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. View Configuration (`master_order_views.xml`)
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<field name="image_1920" widget="image_paste"
|
||||||
|
options="{'size': [50, 50]}"
|
||||||
|
optional="show"
|
||||||
|
help="Click to focus, then Ctrl+V to paste image from Excel"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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() |
|
||||||
Reference in New Issue
Block a user