diff --git a/addons/at_master_order/IMAGE_PASTE_FEATURE.md b/addons/at_master_order/IMAGE_PASTE_FEATURE.md new file mode 100644 index 0000000..ac55914 --- /dev/null +++ b/addons/at_master_order/IMAGE_PASTE_FEATURE.md @@ -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 + +
+ + +
+
+``` + +### 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() |