Initial commit: OCA Financial packages (186 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:04 +02:00
commit 3e0e8473fb
8757 changed files with 947473 additions and 0 deletions

View file

@ -0,0 +1,47 @@
# Sales Stock Picking Invocing
Odoo addon: sale_stock_picking_invoicing
## Installation
```bash
pip install odoo-bringout-oca-account-invoicing-sale_stock_picking_invoicing
```
## Dependencies
This addon depends on:
- sale_management
- sale_stock
- stock_picking_invoicing
- stock_picking_invoice_link
## Manifest Information
- **Name**: Sales Stock Picking Invocing
- **Version**: 16.0.1.0.1
- **Category**: Warehouse Management
- **License**: AGPL-3
- **Installable**: True
## Source
Based on [OCA/account-invoicing](https://github.com/OCA/account-invoicing) branch 16.0, addon `sale_stock_picking_invoicing`.
## License
This package maintains the original AGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Sale_stock_picking_invoicing Module - sale_stock_picking_invoicing
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for sale_stock_picking_invoicing. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,8 @@
# Dependencies
This addon depends on:
- [sale_management](../../odoo-bringout-oca-ocb-sale_management)
- [sale_stock](../../odoo-bringout-oca-ocb-sale_stock)
- [stock_picking_invoicing](../../odoo-bringout-oca-account-invoicing-stock_picking_invoicing)
- [stock_picking_invoice_link](../../odoo-bringout-oca-stock-logistics-workflow-stock_picking_invoice_link)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon sale_stock_picking_invoicing or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-account-invoicing-sale_stock_picking_invoicing"
# or
uv pip install odoo-bringout-oca-account-invoicing-sale_stock_picking_invoicing"
```

View file

@ -0,0 +1,17 @@
# Models
Detected core models and extensions in sale_stock_picking_invoicing.
```mermaid
classDiagram
class res_company
class res_config_settings
class sale_order
class sale_order_line
class stock_move
class stock_picking
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: sale_stock_picking_invoicing. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon sale_stock_picking_invoicing
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,8 @@
# Security
This module does not define custom security rules or access controls beyond Odoo defaults.
Default Odoo security applies:
- Base user access through standard groups
- Model access inherited from dependencies
- No custom row-level security rules

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon sale_stock_picking_invoicing
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,45 @@
[project]
name = "odoo-bringout-oca-account-invoicing-sale_stock_picking_invoicing"
version = "16.0.0"
description = "Sales Stock Picking Invocing - Odoo addon"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-account-invoicing-sale_management>=16.0.0",
"odoo-bringout-oca-account-invoicing-sale_stock>=16.0.0",
"odoo-bringout-oca-account-invoicing-stock_picking_invoicing>=16.0.0",
"odoo-bringout-oca-account-invoicing-stock_picking_invoice_link>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["sale_stock_picking_invoicing"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,160 @@
============================
Sales Stock Picking Invocing
============================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:2c14793fdb5a5bca2858a4db80020eff35109d87c35e92cfc63098e219719eaf
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount--invoicing-lightgray.png?logo=github
:target: https://github.com/OCA/account-invoicing/tree/16.0/sale_stock_picking_invoicing
:alt: OCA/account-invoicing
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/account-invoicing-16-0/account-invoicing-16-0-sale_stock_picking_invoicing
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/account-invoicing&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module extends Stock Picking Invoicing implementation to Sale, you can define the 'Sale Invoicing Policy':
* If set to Sale Order, keep native Odoo behaviour for creation of invoices from Sale Orders.
* If set to Stock Picking, disallow creation of Invoices from Sale Orders for the cases where the Product Type are 'Product', in case of 'Service' still will be possible create from Sale Order.
For stock.moves, override price calculation that is present in stock_picking_invoicing, with the native Sale Order Line price calculation, same for the partner_id and other informations used to create the Invoice from Sale Order as such Payment Terms, Down Payments, Incoterm, Client Ref,etc by using sale methods to get data in order to avoid the necessity of 'glue modules' (small modules made just to avoid indirect dependencies), so in the case of any module include a new field in Invoice created by Sale this field also be include when created by Picking, for example the modules `Account Payment Sale`_ and `Sale Commission`_.
.. _`Account Payment Sale`: https://github.com/OCA/bank-payment/tree/14.0/account_payment_sale
.. _`Sale Commission`: https://github.com/OCA/commission/tree/14.0/sale_commission
**Table of contents**
.. contents::
:local:
Installation
============
This module depends on:
* sale_management
* sale_stock
* stock_picking_invoicing
* stock_picking_invoice_link
Configuration
=============
Define 'Sale Invoicing Policy', if the invoice should be created from Sale Order or from Stock Picking, go to:
**Settings > Users & Companies > Companies**
or
**Sales > Configuration > Settings in Invoicing**
Usage
=====
If 'Stock Picking' is choose as Policy the creation of Invoice from Sale Order works only for Service lines, in the case of Sale Order has Products and Service lines will be create two Invoices.
Known issues / Roadmap
======================
* It is be possible reference multiple sale lines in only one invoice line, but there are a problem the field qty_invoiced in sale.order.line show the quantity of invoice line without consider, in this case, that the value is the sum of others sale lines https://github.com/odoo/odoo/blob/14.0/addons/sale/models/sale.py#L1230, what can make confuse the user about the real Invoiced Quantity, reference https://github.com/odoo/odoo/pull/77195
Changelog
=========
16.0.1.0.0 (2025-01-14)
~~~~~~~~~~~~~~~~~~~~~~~
* Migration to version 16.0
15.0.1.0.0 (2024-10-25)
~~~~~~~~~~~~~~~~~~~~~~~
* Migration to version 15.0 .
14.0.1.0.0 (2024-03-12)
~~~~~~~~~~~~~~~~~~~~~~~
* [ADD] Module sale_stock_picking_invoicing based in l10n_br_sale_stock https://github.com/OCA/l10n-brazil/tree/14.0/l10n_br_sale_stock .
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-invoicing/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/account-invoicing/issues/new?body=module:%20sale_stock_picking_invoicing%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Akretion
Contributors
~~~~~~~~~~~~
* `Akretion <https://akretion.com>`_:
* Renato Lima <renato.lima@akretion.com.br>
* Raphaël Valyi <raphael.valyi@akretion.com.br>
* Magno Costa <magno.costa@akretion.com.br>
* `KMEE <https://www.kmee.com.br>`_:
* Gabriel Cardoso de Faria <gabriel.cardoso@kmee.com.br>
Other credits
~~~~~~~~~~~~~
The development of this module has been financially supported by:
* Aketion - www.akretion.com
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-mbcosta| image:: https://github.com/mbcosta.png?size=40px
:target: https://github.com/mbcosta
:alt: mbcosta
.. |maintainer-renatonlima| image:: https://github.com/renatonlima.png?size=40px
:target: https://github.com/renatonlima
:alt: renatonlima
Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-mbcosta| |maintainer-renatonlima|
This module is part of the `OCA/account-invoicing <https://github.com/OCA/account-invoicing/tree/16.0/sale_stock_picking_invoicing>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1,2 @@
from . import models
from . import wizards

View file

@ -0,0 +1,32 @@
# Copyright (C) 2013-Today - Akretion (<http://www.akretion.com>).
# @author Renato Lima <renato.lima@akretion.com.br>
# @author Raphael Valyi <raphael.valyi@akretion.com>
# @author Magno Costa <magno.costa@akretion.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Sales Stock Picking Invocing",
"category": "Warehouse Management",
"license": "AGPL-3",
"author": "Akretion, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/account-invoicing",
"version": "16.0.1.0.1",
"maintainers": ["mbcosta", "renatonlima"],
"depends": [
"sale_management",
"sale_stock",
"stock_picking_invoicing",
"stock_picking_invoice_link",
],
"data": [
"views/res_company_view.xml",
"views/res_config_settings_view.xml",
# Wizards
"wizards/stock_invoice_onshipping_view.xml",
],
"demo": [
"demo/sale_order_demo.xml",
],
"installable": True,
"auto_install": False,
}

View file

@ -0,0 +1,315 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2022-TODAY Akretion (http://www.akretion.com/)
@author: Magno Costa <magno.costa@akretion.com.br>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo noupdate="1">
<!-- Show partner_shipping_id in Sale Order -->
<record
id="group_sale_delivery_address_config_settings"
model="res.config.settings"
>
<field name="group_sale_delivery_address">True</field>
</record>
<function model="res.config.settings" name="execute">
<value
model="res.config.settings"
search="[('id', '=',
ref('group_sale_delivery_address_config_settings'))]"
/>
</function>
<!-- Show Incoterms in Sale Order and Invoice -->
<record id="group_display_incoterm_config_settings" model="res.config.settings">
<field name="group_display_incoterm">True</field>
</record>
<function model="res.config.settings" name="execute">
<value
model="res.config.settings"
search="[('id', '=',
ref('group_display_incoterm_config_settings'))]"
/>
</function>
<!-- Test different Payment Terms -->
<record id="base.res_partner_2" model="res.partner">
<field
name="property_payment_term_id"
ref="account.account_payment_term_15days"
/>
</record>
<!-- Product is Service Type there is not Delivery Order in this cases,
it's seems a mistake, fix here to use in the tests -->
<record id="product.product_product_1" model="product.product">
<field name="invoice_policy">order</field>
</record>
<!-- Test with different shipping address -->
<record id="res_partner_2_address" model="res.partner">
<field eval="1" name="active" />
<field name="is_company" eval="1" />
<field name="name">Deco Addict - Delivery Address</field>
<field name="parent_id" ref="base.res_partner_2" />
<field
eval="[(6, 0, [ref('base.res_partner_category_14')])]"
name="category_id"
/>
<field name="street">77 Palos Verdes Rd</field>
<field name="city">Pleasant Hill</field>
<field name="state_id" ref='base.state_us_5' />
<field name="zip">94521</field>
<field name="country_id" ref="base.us" />
<field name="type">delivery</field>
<field name="email">deco.addict83@example.com</field>
<field name="phone">(603)-996-3821</field>
<field name="website">http://www.deco-addict.com</field>
<field
name="image_1920"
type="base64"
file="base/static/img/res_partner_2-image.png"
/>
</record>
<!-- Main Company - Different Shipping Address -->
<record id="main_company-sale_order_1" model="sale.order">
<field name="partner_id" ref="base.res_partner_2" />
<field name="partner_invoice_id" ref="base.res_partner_2" />
<field name="partner_shipping_id" ref="res_partner_2_address" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="payment_term_id" ref="account.account_payment_term_advance" />
<field name="team_id" ref="sales_team.crm_team_1" />
<field name="state">draft</field>
<field name="company_id" ref="base.main_company" />
<field
name="note"
>sale_stock_picking_invoicing - Different Delivery and Invoice Address 1</field>
<field name="client_order_ref">Customer Ref Test 1</field>
<field name="campaign_id" ref="utm.utm_campaign_christmas_special" />
<field name="medium_id" ref="utm.utm_medium_banner" />
<field name="source_id" ref="utm.utm_source_monster" />
<field name="incoterm" ref="account.incoterm_FOB" />
<field name="tag_ids" eval="[(4, ref('sales_team.categ_oppor1'))]" />
</record>
<record id="main_company-sale_order_line_1_1" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_1" />
<field
name="name"
model="sale.order.line"
eval="obj().env.ref('product.product_product_27').get_product_multiline_description_sale()"
/>
<field name="product_id" ref="product.product_product_27" />
<field name="product_uom_qty">2</field>
<field name="product_uom" ref="uom.product_uom_unit" />
<field name="price_unit">500</field>
</record>
<record id="main_company-sale_order_line_1_2" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_1" />
<field name="name">This is a Note 1</field>
<field name="display_type">line_note</field>
</record>
<record id="main_company-sale_order_line_1_3" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_1" />
<field name="name">This is a Section 1</field>
<field name="display_type">line_section</field>
</record>
<record id="main_company-sale_order_line_1_4" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_1" />
<field
name="name"
model="sale.order.line"
eval="obj().env.ref('product.product_product_12').get_product_multiline_description_sale()"
/>
<field name="product_id" ref="product.product_product_12" />
<field name="product_uom_qty">2</field>
<field name="product_uom" ref="uom.product_uom_unit" />
<field name="price_unit">500</field>
</record>
<!-- Sale Order with Product and Service -->
<record id="main_company-sale_order_2" model="sale.order">
<field name="partner_id" ref="base.res_partner_2" />
<field name="partner_invoice_id" ref="base.res_partner_2" />
<field name="partner_shipping_id" ref="res_partner_2_address" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="payment_term_id" ref="account.account_payment_term_advance" />
<field name="team_id" ref="sales_team.crm_team_1" />
<field name="state">draft</field>
<field name="company_id" ref="base.main_company" />
<field name="note">sale_stock_picking_invoicing - Product and Service</field>
<field name="client_order_ref">Customer Ref Test 2</field>
<field name="campaign_id" ref="utm.utm_campaign_christmas_special" />
<field name="medium_id" ref="utm.utm_medium_banner" />
<field name="source_id" ref="utm.utm_source_monster" />
<field name="incoterm" ref="account.incoterm_FOB" />
<field
name="tag_ids"
eval="[(4, ref('sales_team.categ_oppor1')), (4, ref('sales_team.categ_oppor3'))]"
/>
</record>
<record id="main_company-sale_order_line_2_1" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_2" />
<field
name="name"
model="sale.order.line"
eval="obj().env.ref('product.product_product_27').get_product_multiline_description_sale()"
/>
<field name="product_id" ref="product.product_product_27" />
<field name="product_uom_qty">2</field>
<field name="product_uom" ref="uom.product_uom_unit" />
<field name="price_unit">500</field>
</record>
<record id="main_company-sale_order_line_2_2" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_2" />
<field name="name">This is a Note 2</field>
<field name="display_type">line_note</field>
</record>
<record id="main_company-sale_order_line_2_3" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_2" />
<field name="name">This is a Section 2</field>
<field name="display_type">line_section</field>
</record>
<record id="main_company-sale_order_line_2_4" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_2" />
<field
name="name"
model="sale.order.line"
eval="obj().env.ref('product.product_product_1').get_product_multiline_description_sale()"
/>
<field name="product_id" ref="product.product_product_1" />
<field name="product_uom_qty">10</field>
<field name="product_uom" ref="uom.product_uom_hour" />
<field name="price_unit">100</field>
</record>
<!-- Groupping Pickings Test -->
<record id="main_company-sale_order_3" model="sale.order">
<field name="partner_id" ref="base.res_partner_2" />
<field name="partner_invoice_id" ref="base.res_partner_2" />
<field name="partner_shipping_id" ref="base.res_partner_2" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="payment_term_id" ref="account.account_payment_term_advance" />
<field name="team_id" ref="sales_team.crm_team_1" />
<field name="state">draft</field>
<field name="company_id" ref="base.main_company" />
<field name="note">sale_stock_picking_invoicing - Grouping Pickings 3</field>
<field name="client_order_ref">Customer Ref Test 3</field>
<field name="campaign_id" ref="utm.utm_campaign_christmas_special" />
<field name="medium_id" ref="utm.utm_medium_banner" />
<field name="source_id" ref="utm.utm_source_monster" />
<field name="incoterm" ref="account.incoterm_FOB" />
<field name="tag_ids" eval="[(4, ref('sales_team.categ_oppor1'))]" />
</record>
<record id="main_company-sale_order_line_3_1" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_3" />
<field
name="name"
model="sale.order.line"
eval="obj().env.ref('product.product_product_27').get_product_multiline_description_sale()"
/>
<field name="product_id" ref="product.product_product_27" />
<field name="product_uom_qty">2</field>
<field name="product_uom" ref="uom.product_uom_unit" />
<field name="price_unit">500</field>
</record>
<record id="main_company-sale_order_line_3_2" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_3" />
<field name="name">This is a Note 3</field>
<field name="display_type">line_note</field>
</record>
<record id="main_company-sale_order_line_3_3" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_3" />
<field name="name">This is a Section 3</field>
<field name="display_type">line_section</field>
</record>
<record id="main_company-sale_order_line_3_4" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_3" />
<field
name="name"
model="sale.order.line"
eval="obj().env.ref('product.product_product_12').get_product_multiline_description_sale()"
/>
<field name="product_id" ref="product.product_product_12" />
<field name="product_uom_qty">2</field>
<field name="product_uom" ref="uom.product_uom_unit" />
<field name="price_unit">500</field>
</record>
<!-- Groupping Pickings Test -->
<record id="main_company-sale_order_4" model="sale.order">
<field name="partner_id" ref="base.res_partner_2" />
<field name="partner_invoice_id" ref="base.res_partner_2" />
<field name="partner_shipping_id" ref="base.res_partner_2" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="payment_term_id" ref="account.account_payment_term_advance" />
<field name="team_id" ref="sales_team.crm_team_1" />
<field name="state">draft</field>
<field name="company_id" ref="base.main_company" />
<field name="note">sale_stock_picking_invoicing - Grouping Pickings 4</field>
<field name="client_order_ref">Customer Ref Test 4</field>
<field name="campaign_id" ref="utm.utm_campaign_christmas_special" />
<field name="medium_id" ref="utm.utm_medium_banner" />
<field name="source_id" ref="utm.utm_source_monster" />
<field name="incoterm" ref="account.incoterm_FOB" />
<field name="tag_ids" eval="[(4, ref('sales_team.categ_oppor1'))]" />
</record>
<record id="main_company-sale_order_line_4_1" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_4" />
<field
name="name"
model="sale.order.line"
eval="obj().env.ref('product.product_product_27').get_product_multiline_description_sale()"
/>
<field name="product_id" ref="product.product_product_27" />
<field name="product_uom_qty">2</field>
<field name="product_uom" ref="uom.product_uom_unit" />
<field name="price_unit">500</field>
</record>
<record id="main_company-sale_order_line_4_2" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_4" />
<field name="name">This is a Note 4</field>
<field name="display_type">line_note</field>
</record>
<record id="main_company-sale_order_line_4_3" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_4" />
<field name="name">This is a Section 4</field>
<field name="display_type">line_section</field>
</record>
<record id="main_company-sale_order_line_4_4" model="sale.order.line">
<field name="order_id" ref="main_company-sale_order_4" />
<field
name="name"
model="sale.order.line"
eval="obj().env.ref('product.product_product_12').get_product_multiline_description_sale()"
/>
<field name="product_id" ref="product.product_product_12" />
<field name="product_uom_qty">2</field>
<field name="product_uom" ref="uom.product_uom_unit" />
<field name="price_unit">500</field>
</record>
</odoo>

View file

@ -0,0 +1,104 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_stock_picking_invoicing
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_res_company
msgid "Companies"
msgstr "Kompanije"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_res_config_settings
msgid "Config Settings"
msgstr "Postavke"
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_stock_invoice_onshipping__deduct_down_payments
msgid "Deduct down payments"
msgstr "Odbij predujmove"
#. module: sale_stock_picking_invoicing
#: model_terms:ir.ui.view,arch_db:sale_stock_picking_invoicing.sale_stock_picking_invoicing_res_config_settings_form
msgid ""
"Define if Invoice should be created from Sale Order or from Stock Picking"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_stock_invoice_onshipping__has_down_payments
msgid "Has down payments"
msgstr "Ima predujam"
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,help:sale_stock_picking_invoicing.field_res_company__sale_invoicing_policy
#: model:ir.model.fields,help:sale_stock_picking_invoicing.field_res_config_settings__sale_invoicing_policy
msgid ""
"If set to Sale Order, keep native Odoo behaviour for creation of invoices from Sale Orders.\n"
"If set to Stock Picking, disallow creation of Invoices from Sale Orders for the cases where Product Type are 'Product', in case of 'Service' still will be possible create from Sale Order."
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_res_company__sale_invoicing_policy
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_res_config_settings__sale_invoicing_policy
msgid "Sale Invoicing Policy"
msgstr "Sale Invoicing Policy"
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields.selection,name:sale_stock_picking_invoicing.selection__res_company__sale_invoicing_policy__sale_order
msgid "Sale Order"
msgstr "Prodajni nalog"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_sale_order
msgid "Sales Order"
msgstr "Prodajni nalog"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_sale_order_line
msgid "Sales Order Line"
msgstr "Stavka prodajne narudžbe"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_stock_invoice_onshipping
msgid "Stock Invoice Onshipping"
msgstr "Stock Invoice Onshipping"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_stock_move
msgid "Stock Move"
msgstr "Skladišno kretanje"
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields.selection,name:sale_stock_picking_invoicing.selection__res_company__sale_invoicing_policy__stock_picking
msgid "Stock Picking"
msgstr "Preuzimanje u skladištu"
#. module: sale_stock_picking_invoicing
#: model_terms:ir.ui.view,arch_db:sale_stock_picking_invoicing.sale_stock_picking_invoicing_res_config_settings_form
msgid "This default value is applied to creation of Invoice."
msgstr "This default value is applied to creation of Invoice."
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_stock_picking
msgid "Transfer"
msgstr "Prijenos"
#. module: sale_stock_picking_invoicing
#. odoo-python
#: code:addons/sale_stock_picking_invoicing/models/sale_order.py:0
#, python-format
msgid ""
"When 'Sale Invoicing Policy' is defined as'Stock Picking' the Invoice can "
"only be created from the Stock Picking, if necessary you can change in the "
"Company or Sale Settings."
msgstr ""

View file

@ -0,0 +1,118 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_stock_picking_invoicing
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-02-09 23:06+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.6.2\n"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_res_company
msgid "Companies"
msgstr "Aziende"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_res_config_settings
msgid "Config Settings"
msgstr "Impostazioni configurazione"
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_stock_invoice_onshipping__deduct_down_payments
msgid "Deduct down payments"
msgstr "Dedurre acconti"
#. module: sale_stock_picking_invoicing
#: model_terms:ir.ui.view,arch_db:sale_stock_picking_invoicing.sale_stock_picking_invoicing_res_config_settings_form
msgid ""
"Define if Invoice should be created from Sale Order or from Stock Picking"
msgstr ""
"Definire se la fattura deve essere creata dall'ordine di vendita o dal "
"prelievo di magazzino"
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_stock_invoice_onshipping__has_down_payments
msgid "Has down payments"
msgstr "Ha acconti"
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,help:sale_stock_picking_invoicing.field_res_company__sale_invoicing_policy
#: model:ir.model.fields,help:sale_stock_picking_invoicing.field_res_config_settings__sale_invoicing_policy
msgid ""
"If set to Sale Order, keep native Odoo behaviour for creation of invoices from Sale Orders.\n"
"If set to Stock Picking, disallow creation of Invoices from Sale Orders for the cases where Product Type are 'Product', in case of 'Service' still will be possible create from Sale Order."
msgstr ""
"Se impostato su ordine di vendita, mantenere il comportamento nativo di Odoo "
"per la creazione di fatture da ordini di vendita. \n"
"Se impostato su prelievo di magazzino, non consente la creazione di fatture "
"da ordini di vendita nei casi in cui il tipo di prodotto è 'Prodotto', in "
"caso di 'Servizio' sarà comunque possibile crearle da ordine di vendita."
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_res_company__sale_invoicing_policy
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_res_config_settings__sale_invoicing_policy
msgid "Sale Invoicing Policy"
msgstr "Politica fatturazione vendite"
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields.selection,name:sale_stock_picking_invoicing.selection__res_company__sale_invoicing_policy__sale_order
msgid "Sale Order"
msgstr "Ordine di vendita"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_sale_order
msgid "Sales Order"
msgstr "Ordine di vendita"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_sale_order_line
msgid "Sales Order Line"
msgstr "Riga ordine di vendita"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_stock_invoice_onshipping
msgid "Stock Invoice Onshipping"
msgstr "Fattura su spedizione"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_stock_move
msgid "Stock Move"
msgstr "Movimento di magazzino"
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields.selection,name:sale_stock_picking_invoicing.selection__res_company__sale_invoicing_policy__stock_picking
msgid "Stock Picking"
msgstr "Prelievo di magazzino"
#. module: sale_stock_picking_invoicing
#: model_terms:ir.ui.view,arch_db:sale_stock_picking_invoicing.sale_stock_picking_invoicing_res_config_settings_form
msgid "This default value is applied to creation of Invoice."
msgstr "Questo valore predefinito è applicato alla creazione della fattura."
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_stock_picking
msgid "Transfer"
msgstr "Trasferimento"
#. module: sale_stock_picking_invoicing
#. odoo-python
#: code:addons/sale_stock_picking_invoicing/models/sale_order.py:0
#, python-format
msgid ""
"When 'Sale Invoicing Policy' is defined as'Stock Picking' the Invoice can "
"only be created from the Stock Picking, if necessary you can change in the "
"Company or Sale Settings."
msgstr ""
"Quando la \"Politica di fatturazione delle vendite\" è definita come "
"\"Prelievo di magazzino\", la fattura può essere creata solo dal prelievo di "
"magazzino; se necessario, è possibile apportare modifiche nelle Impostazioni "
"aziendali o di vendita."

View file

@ -0,0 +1,104 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_stock_picking_invoicing
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_res_company
msgid "Companies"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_stock_invoice_onshipping__deduct_down_payments
msgid "Deduct down payments"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model_terms:ir.ui.view,arch_db:sale_stock_picking_invoicing.sale_stock_picking_invoicing_res_config_settings_form
msgid ""
"Define if Invoice should be created from Sale Order or from Stock Picking"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_stock_invoice_onshipping__has_down_payments
msgid "Has down payments"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,help:sale_stock_picking_invoicing.field_res_company__sale_invoicing_policy
#: model:ir.model.fields,help:sale_stock_picking_invoicing.field_res_config_settings__sale_invoicing_policy
msgid ""
"If set to Sale Order, keep native Odoo behaviour for creation of invoices from Sale Orders.\n"
"If set to Stock Picking, disallow creation of Invoices from Sale Orders for the cases where Product Type are 'Product', in case of 'Service' still will be possible create from Sale Order."
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_res_company__sale_invoicing_policy
#: model:ir.model.fields,field_description:sale_stock_picking_invoicing.field_res_config_settings__sale_invoicing_policy
msgid "Sale Invoicing Policy"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields.selection,name:sale_stock_picking_invoicing.selection__res_company__sale_invoicing_policy__sale_order
msgid "Sale Order"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_sale_order_line
msgid "Sales Order Line"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_stock_invoice_onshipping
msgid "Stock Invoice Onshipping"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_stock_move
msgid "Stock Move"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model.fields.selection,name:sale_stock_picking_invoicing.selection__res_company__sale_invoicing_policy__stock_picking
msgid "Stock Picking"
msgstr ""
#. module: sale_stock_picking_invoicing
#: model_terms:ir.ui.view,arch_db:sale_stock_picking_invoicing.sale_stock_picking_invoicing_res_config_settings_form
msgid "This default value is applied to creation of Invoice."
msgstr ""
#. module: sale_stock_picking_invoicing
#: model:ir.model,name:sale_stock_picking_invoicing.model_stock_picking
msgid "Transfer"
msgstr ""
#. module: sale_stock_picking_invoicing
#. odoo-python
#: code:addons/sale_stock_picking_invoicing/models/sale_order.py:0
#, python-format
msgid ""
"When 'Sale Invoicing Policy' is defined as'Stock Picking' the Invoice can "
"only be created from the Stock Picking, if necessary you can change in the "
"Company or Sale Settings."
msgstr ""

View file

@ -0,0 +1,6 @@
from . import sale_order_line
from . import stock_move
from . import sale_order
from . import stock_picking
from . import res_company
from . import res_config_settings

View file

@ -0,0 +1,34 @@
# Copyright (C) 2021-TODAY Akretion
# @author Magno Costa <magno.costa@akretion.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class ResCompany(models.Model):
_inherit = "res.company"
@api.model
def _default_sale_invoicing_policy(self):
# In order to avoid errors in the CI tests environment when Created
# Invoice from Sale Order using sale.advance.payment.inv object
# is necessary let default policy as sale_order
# TODO: Is there other form to avoid this problem?
result = "stock_picking"
module_base = self.env["ir.module.module"].search([("name", "=", "base")])
if module_base.demo:
result = "sale_order"
return result
sale_invoicing_policy = fields.Selection(
selection=[
("sale_order", "Sale Order"),
("stock_picking", "Stock Picking"),
],
help="If set to Sale Order, keep native Odoo behaviour for creation of"
" invoices from Sale Orders.\n"
"If set to Stock Picking, disallow creation of Invoices from Sale Orders"
" for the cases where Product Type are 'Product', in case of 'Service'"
" still will be possible create from Sale Order.",
default=_default_sale_invoicing_policy,
)

View file

@ -0,0 +1,13 @@
# Copyright (C) 2021-TODAY Akretion
# @author Magno Costa <magno.costa@akretion.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
sale_invoicing_policy = fields.Selection(
related="company_id.sale_invoicing_policy", readonly=False
)

View file

@ -0,0 +1,37 @@
# Copyright (C) 2020-TODAY Akretion
# @author Magno Costa <magno.costa@akretion.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, models
from odoo.exceptions import UserError
class SaleOrder(models.Model):
_inherit = "sale.order"
def _get_invoiceable_lines(self, final=False):
"""Return the invoiceable lines for order `self`."""
lines = super()._get_invoiceable_lines(final)
model = self.env.context.get("active_model")
if (
self.company_id.sale_invoicing_policy == "stock_picking"
and model != "stock.picking"
):
new_lines = lines.filtered(
lambda ln: ln.product_id.type != "product" and not ln.is_downpayment
)
if new_lines:
# Case lines with Product Type 'service'
lines = new_lines
else:
# Case only Products Type 'product'
raise UserError(
_(
"When 'Sale Invoicing Policy' is defined as"
"'Stock Picking' the Invoice can only be created"
" from the Stock Picking, if necessary you can change"
" in the Company or Sale Settings."
)
)
return lines

View file

@ -0,0 +1,18 @@
# Copyright (C) 2013-Today - Akretion (<http://www.akretion.com>).
# @author Renato Lima <renato.lima@akretion.com.br>
# @author Raphael Valyi <raphael.valyi@akretion.com>
# @author Magno Costa <magno.costa@akretion.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
def _prepare_procurement_values(self, group_id=False):
values = super()._prepare_procurement_values(group_id)
if self.order_id.company_id.sale_invoicing_policy == "stock_picking":
values["invoice_state"] = "2binvoiced"
return values

View file

@ -0,0 +1,29 @@
# Copyright (C) 2020-TODAY KMEE
# @author Gabriel Cardoso de Faria <gabriel.cardoso@kmee.com.br>
# Copyright (C) 2021-TODAY Akretion
# @author Magno Costa <magno.costa@akretion.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class StockMove(models.Model):
_inherit = "stock.move"
def _get_price_unit_invoice(self, inv_type, partner, qty=1):
result = super()._get_price_unit_invoice(inv_type, partner, qty)
move = fields.first(self)
if move.sale_line_id and move.sale_line_id.price_unit != result:
result = move.sale_line_id.price_unit
return result
def _get_new_picking_values(self):
values = super()._get_new_picking_values()
move = fields.first(self)
if move.sale_line_id:
company = move.sale_line_id.order_id.company_id
if company.sale_invoicing_policy == "stock_picking":
values["invoice_state"] = "2binvoiced"
return values

View file

@ -0,0 +1,16 @@
# Copyright (C) 2021-TODAY Akretion
# @author Magno Costa <magno.costa@akretion.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
class StockPicking(models.Model):
_inherit = "stock.picking"
def _get_partner_to_invoice(self):
partner_id = super()._get_partner_to_invoice()
if self.sale_id:
partner_id = self.sale_id.partner_invoice_id.id
return partner_id

View file

@ -0,0 +1,7 @@
Define 'Sale Invoicing Policy', if the invoice should be created from Sale Order or from Stock Picking, go to:
**Settings > Users & Companies > Companies**
or
**Sales > Configuration > Settings in Invoicing**

View file

@ -0,0 +1,9 @@
* `Akretion <https://akretion.com>`_:
* Renato Lima <renato.lima@akretion.com.br>
* Raphaël Valyi <raphael.valyi@akretion.com.br>
* Magno Costa <magno.costa@akretion.com.br>
* `KMEE <https://www.kmee.com.br>`_:
* Gabriel Cardoso de Faria <gabriel.cardoso@kmee.com.br>

View file

@ -0,0 +1,3 @@
The development of this module has been financially supported by:
* Aketion - www.akretion.com

View file

@ -0,0 +1,10 @@
This module extends Stock Picking Invoicing implementation to Sale, you can define the 'Sale Invoicing Policy':
* If set to Sale Order, keep native Odoo behaviour for creation of invoices from Sale Orders.
* If set to Stock Picking, disallow creation of Invoices from Sale Orders for the cases where the Product Type are 'Product', in case of 'Service' still will be possible create from Sale Order.
For stock.moves, override price calculation that is present in stock_picking_invoicing, with the native Sale Order Line price calculation, same for the partner_id and other informations used to create the Invoice from Sale Order as such Payment Terms, Down Payments, Incoterm, Client Ref,etc by using sale methods to get data in order to avoid the necessity of 'glue modules' (small modules made just to avoid indirect dependencies), so in the case of any module include a new field in Invoice created by Sale this field also be include when created by Picking, for example the modules `Account Payment Sale`_ and `Sale Commission`_.
.. _`Account Payment Sale`: https://github.com/OCA/bank-payment/tree/14.0/account_payment_sale
.. _`Sale Commission`: https://github.com/OCA/commission/tree/14.0/sale_commission

View file

@ -0,0 +1,14 @@
16.0.1.0.0 (2025-01-14)
~~~~~~~~~~~~~~~~~~~~~~~
* Migration to version 16.0
15.0.1.0.0 (2024-10-25)
~~~~~~~~~~~~~~~~~~~~~~~
* Migration to version 15.0 .
14.0.1.0.0 (2024-03-12)
~~~~~~~~~~~~~~~~~~~~~~~
* [ADD] Module sale_stock_picking_invoicing based in l10n_br_sale_stock https://github.com/OCA/l10n-brazil/tree/14.0/l10n_br_sale_stock .

View file

@ -0,0 +1,6 @@
This module depends on:
* sale_management
* sale_stock
* stock_picking_invoicing
* stock_picking_invoice_link

View file

@ -0,0 +1 @@
* It is be possible reference multiple sale lines in only one invoice line, but there are a problem the field qty_invoiced in sale.order.line show the quantity of invoice line without consider, in this case, that the value is the sum of others sale lines https://github.com/odoo/odoo/blob/14.0/addons/sale/models/sale.py#L1230, what can make confuse the user about the real Invoiced Quantity, reference https://github.com/odoo/odoo/pull/77195

View file

@ -0,0 +1 @@
If 'Stock Picking' is choose as Policy the creation of Invoice from Sale Order works only for Service lines, in the case of Sale Order has Products and Service lines will be create two Invoices.

View file

@ -0,0 +1,505 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Sales Stock Picking Invocing</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="sales-stock-picking-invocing">
<h1 class="title">Sales Stock Picking Invocing</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:2c14793fdb5a5bca2858a4db80020eff35109d87c35e92cfc63098e219719eaf
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/account-invoicing/tree/16.0/sale_stock_picking_invoicing"><img alt="OCA/account-invoicing" src="https://img.shields.io/badge/github-OCA%2Faccount--invoicing-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/account-invoicing-16-0/account-invoicing-16-0-sale_stock_picking_invoicing"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/account-invoicing&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module extends Stock Picking Invoicing implementation to Sale, you can define the Sale Invoicing Policy:</p>
<ul class="simple">
<li>If set to Sale Order, keep native Odoo behaviour for creation of invoices from Sale Orders.</li>
<li>If set to Stock Picking, disallow creation of Invoices from Sale Orders for the cases where the Product Type are Product, in case of Service still will be possible create from Sale Order.</li>
</ul>
<p>For stock.moves, override price calculation that is present in stock_picking_invoicing, with the native Sale Order Line price calculation, same for the partner_id and other informations used to create the Invoice from Sale Order as such Payment Terms, Down Payments, Incoterm, Client Ref,etc by using sale methods to get data in order to avoid the necessity of glue modules (small modules made just to avoid indirect dependencies), so in the case of any module include a new field in Invoice created by Sale this field also be include when created by Picking, for example the modules <a class="reference external" href="https://github.com/OCA/bank-payment/tree/14.0/account_payment_sale">Account Payment Sale</a> and <a class="reference external" href="https://github.com/OCA/commission/tree/14.0/sale_commission">Sale Commission</a>.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#installation" id="toc-entry-1">Installation</a></li>
<li><a class="reference internal" href="#configuration" id="toc-entry-2">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-3">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-4">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#changelog" id="toc-entry-5">Changelog</a><ul>
<li><a class="reference internal" href="#section-1" id="toc-entry-6">16.0.1.0.0 (2025-01-14)</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-7">15.0.1.0.0 (2024-10-25)</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-8">14.0.1.0.0 (2024-03-12)</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-9">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-10">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-11">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-12">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-13">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-14">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h1><a class="toc-backref" href="#toc-entry-1">Installation</a></h1>
<p>This module depends on:</p>
<ul class="simple">
<li>sale_management</li>
<li>sale_stock</li>
<li>stock_picking_invoicing</li>
<li>stock_picking_invoice_link</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-2">Configuration</a></h1>
<p>Define Sale Invoicing Policy, if the invoice should be created from Sale Order or from Stock Picking, go to:</p>
<p><strong>Settings &gt; Users &amp; Companies &gt; Companies</strong></p>
<p>or</p>
<p><strong>Sales &gt; Configuration &gt; Settings in Invoicing</strong></p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-3">Usage</a></h1>
<p>If Stock Picking is choose as Policy the creation of Invoice from Sale Order works only for Service lines, in the case of Sale Order has Products and Service lines will be create two Invoices.</p>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#toc-entry-4">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>It is be possible reference multiple sale lines in only one invoice line, but there are a problem the field qty_invoiced in sale.order.line show the quantity of invoice line without consider, in this case, that the value is the sum of others sale lines <a class="reference external" href="https://github.com/odoo/odoo/blob/14.0/addons/sale/models/sale.py#L1230">https://github.com/odoo/odoo/blob/14.0/addons/sale/models/sale.py#L1230</a>, what can make confuse the user about the real Invoiced Quantity, reference <a class="reference external" href="https://github.com/odoo/odoo/pull/77195">https://github.com/odoo/odoo/pull/77195</a></li>
</ul>
</div>
<div class="section" id="changelog">
<h1><a class="toc-backref" href="#toc-entry-5">Changelog</a></h1>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-6">16.0.1.0.0 (2025-01-14)</a></h2>
<ul class="simple">
<li>Migration to version 16.0</li>
</ul>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-7">15.0.1.0.0 (2024-10-25)</a></h2>
<ul class="simple">
<li>Migration to version 15.0 .</li>
</ul>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-8">14.0.1.0.0 (2024-03-12)</a></h2>
<ul class="simple">
<li>[ADD] Module sale_stock_picking_invoicing based in l10n_br_sale_stock <a class="reference external" href="https://github.com/OCA/l10n-brazil/tree/14.0/l10n_br_sale_stock">https://github.com/OCA/l10n-brazil/tree/14.0/l10n_br_sale_stock</a> .</li>
</ul>
</div>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-9">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/account-invoicing/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/account-invoicing/issues/new?body=module:%20sale_stock_picking_invoicing%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-10">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-11">Authors</a></h2>
<ul class="simple">
<li>Akretion</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-12">Contributors</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://akretion.com">Akretion</a>:<ul>
<li>Renato Lima &lt;<a class="reference external" href="mailto:renato.lima&#64;akretion.com.br">renato.lima&#64;akretion.com.br</a>&gt;</li>
<li>Raphaël Valyi &lt;<a class="reference external" href="mailto:raphael.valyi&#64;akretion.com.br">raphael.valyi&#64;akretion.com.br</a>&gt;</li>
<li>Magno Costa &lt;<a class="reference external" href="mailto:magno.costa&#64;akretion.com.br">magno.costa&#64;akretion.com.br</a>&gt;</li>
</ul>
</li>
<li><a class="reference external" href="https://www.kmee.com.br">KMEE</a>:<ul>
<li>Gabriel Cardoso de Faria &lt;<a class="reference external" href="mailto:gabriel.cardoso&#64;kmee.com.br">gabriel.cardoso&#64;kmee.com.br</a>&gt;</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#toc-entry-13">Other credits</a></h2>
<p>The development of this module has been financially supported by:</p>
<ul class="simple">
<li>Aketion - www.akretion.com</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-14">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainers</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/mbcosta"><img alt="mbcosta" src="https://github.com/mbcosta.png?size=40px" /></a> <a class="reference external image-reference" href="https://github.com/renatonlima"><img alt="renatonlima" src="https://github.com/renatonlima.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/account-invoicing/tree/16.0/sale_stock_picking_invoicing">OCA/account-invoicing</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,435 @@
# Copyright (C) 2021-TODAY Akretion
# @author Magno Costa <magno.costa@akretion.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import exceptions
from odoo.tests import Form
from odoo.addons.stock_picking_invoicing.tests.common import TestPickingInvoicingCommon
class TestSaleStock(TestPickingInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.account_move_model = cls.env["account.move"]
cls.invoice_wizard = cls.env["stock.invoice.onshipping"]
cls.stock_return_picking = cls.env["stock.return.picking"]
cls.stock_picking = cls.env["stock.picking"]
# In order to avoid errors in the tests CI environment when the tests
# Create of Invoice by Sale Order using sale.advance.payment.inv object
# is necessary let default policy as sale_order, just affect demo data.
# TODO: Is there other form to avoid this problem?
cls.companies = cls.env["res.company"].search(
[("sale_invoicing_policy", "=", "sale_order")]
)
for company in cls.companies:
company.sale_invoicing_policy = "stock_picking"
def test_01_sale_stock_return(self):
"""
Test a SO with a product invoiced on delivery. Deliver and invoice
the SO, then do a return of the picking. Check that a refund
invoice is well generated.
"""
# intial so
self.partner = self.env.ref(
"sale_stock_picking_invoicing.res_partner_2_address"
)
self.product = self.env.ref("product.product_delivery_01")
so_vals = {
"partner_id": self.partner.id,
"partner_invoice_id": self.partner.id,
"partner_shipping_id": self.partner.id,
"order_line": [
(
0,
0,
{
"name": self.product.name,
"product_id": self.product.id,
"product_uom_qty": 3.0,
"product_uom": self.product.uom_id.id,
"price_unit": self.product.list_price,
},
)
],
"pricelist_id": self.env.ref("product.list0").id,
}
self.so = self.env["sale.order"].create(so_vals)
# confirm our standard so, check the picking
self.so.action_confirm()
self.assertTrue(
self.so.picking_ids,
'Sale Stock: no picking created for "invoice on '
'delivery" storable products',
)
# set stock.picking to be invoiced
self.assertTrue(
len(self.so.picking_ids) == 1,
"More than one stock " "picking for sale.order",
)
# Check Sale Invoicing Policy Warning to force create Invoice from Picking
with self.assertRaises(exceptions.UserError):
self.so._create_invoices(final=True)
self.so.picking_ids.set_to_be_invoiced()
# validate stock.picking
stock_picking = self.so.picking_ids
# compare sale.order.line with stock.move
stock_move = stock_picking.move_ids
sale_order_line = self.so.order_line
sm_fields = [key for key in self.env["stock.move"]._fields.keys()]
sol_fields = [key for key in self.env["sale.order.line"]._fields.keys()]
skipped_fields = [
"id",
# 'S00029/FURN_7777: Stock>Customers' != 'S00029 - Office Chair'
"display_name",
"state",
# Price Unit in move is different from sale line
# TODO: Should be equal? After Confirmed stock picking
# the value will be change based Stock Valuation
# configuration.
"price_unit",
# There are a diference for field Name
# '[FURN_7777] Office Chair' != 'Office Chair'
"name",
]
common_fields = list(set(sm_fields) & set(sol_fields) - set(skipped_fields))
for field in common_fields:
self.assertEqual(
stock_move[field],
sale_order_line[field],
"Field %s failed to transfer from "
"sale.order.line to stock.move" % field,
)
def test_picking_sale_order_product_and_service(self):
"""
Test Sale Order with product and service
"""
sale_order_form = sale_order_form = Form(
self.env.ref("sale_stock_picking_invoicing.main_company-sale_order_2")
)
# Necessary to get the currency
sale_order_form.pricelist_id = self.env.ref("product.list0")
sale_order_2 = sale_order_form.save()
sale_order_2.action_confirm()
# Method to create invoice in sale order should work only
# for lines where products are of TYPE Service
sale_order_2._create_invoices()
# Should be exist one Invoice
self.assertEqual(1, sale_order_2.invoice_count)
for invoice in sale_order_2.invoice_ids:
line = invoice.invoice_line_ids.filtered(
lambda ln: ln.product_id.type == "service"
)
self.assertEqual(line.product_id.type, "service")
# Invoice of Service
invoice.action_post()
self.assertEqual(
invoice.state, "posted", "Invoice should be in state Posted"
)
picking = sale_order_2.picking_ids
# Only the line of Type Product
self.assertEqual(len(picking.move_ids_without_package), 1)
self.assertEqual(picking.invoice_state, "2binvoiced")
self.picking_move_state(picking)
# Test Create Invoice from Sale when raise UseError
context = {
"active_model": "sale.order",
"active_id": sale_order_2.id,
"active_ids": sale_order_2.ids,
}
payment = (
self.env["sale.advance.payment.inv"]
.with_context(**context)
.create(
{
"advance_payment_method": "delivered",
}
)
)
with self.assertRaises(exceptions.UserError):
payment.with_context(**context).create_invoices()
invoice = self.create_invoice_wizard(picking)
self.assertEqual(picking.invoice_state, "invoiced")
self.assertIn(invoice, picking.invoice_ids)
self.assertIn(picking, invoice.picking_ids)
# Picking with Partner Shipping from Sale Order
self.assertEqual(picking.partner_id, sale_order_2.partner_shipping_id)
# Invoice created with Partner Invoice from Sale Order
self.assertEqual(invoice.partner_id, sale_order_2.partner_invoice_id)
# Invoice created with Partner Shipping from Picking
self.assertEqual(invoice.partner_shipping_id, picking.partner_id)
# When informed Payment Term in Sale Orde should be
# used instead of the default in Partner.
self.assertEqual(invoice.invoice_payment_term_id, sale_order_2.payment_term_id)
# 1 Product 1 Note should be created
self.assertEqual(len(invoice.invoice_line_ids), 2)
# In the Sale Order should be exist two Invoices, one
# for Product other for Service
self.assertEqual(2, sale_order_2.invoice_count)
# Confirm Invoice
invoice.action_post()
self.assertEqual(invoice.state, "posted", "Invoice should be in state Posted.")
# Check Invoiced QTY
for line in sale_order_2.order_line.filtered(
lambda ln: ln.product_id.type == "product"
):
self.assertEqual(line.product_uom_qty, line.qty_invoiced)
# Test the qty_to_invoice
line.product_id.invoice_policy = "order"
self.assertEqual(line.qty_to_invoice, 0.0)
# Check if the Sale Lines fields are equals to Invoice Lines
sol_fields = [key for key in self.env["sale.order.line"]._fields.keys()]
acl_fields = [key for key in self.env["account.move.line"]._fields.keys()]
skipped_fields = [
"id",
"display_name",
"state",
"create_date",
# By th TAX 15% automatic add in invoice the value change
"price_total",
# Necessary after call onchange_partner_id
"write_date",
"__last_update",
# Field sequence add in creation of Invoice
"sequence",
# In the sale.orde.line display_type has only line_section
# and line_note, the acccount.move.line has more options
"display_type",
]
common_fields = list(set(acl_fields) & set(sol_fields) - set(skipped_fields))
sale_order_line = picking.move_ids_without_package.filtered(
lambda ln: ln.sale_line_id
).sale_line_id
invoice_lines = picking.invoice_ids.invoice_line_ids.filtered(
lambda ln: ln.product_id
)
# Necessary for get analytic_precision
# this problem only occours in the tests, by some reason not
# identify yet, but works in the screen the default behavior
with Form(invoice_lines) as line:
line.save()
for field in common_fields:
self.assertEqual(
sale_order_line[field],
invoice_lines[field],
"Field %s failed to transfer from "
"sale.order.line to account.invoice.line" % field,
)
# Return Picking
picking_devolution = self.return_picking_wizard(picking)
self.assertEqual(picking_devolution.invoice_state, "2binvoiced")
for line in picking_devolution.move_ids:
self.assertEqual(line.invoice_state, "2binvoiced")
self.picking_move_state(picking_devolution)
self.assertEqual(picking_devolution.state, "done", "Change state fail.")
invoice_devolution = self.create_invoice_wizard(picking_devolution)
# Confirm Invoice
invoice_devolution.action_post()
self.assertEqual(
invoice_devolution.state, "posted", "Invoice should be in state Posted"
)
# Test need to be comment because there are a problem with module
# sale_line_refund_to_invoice_qty
# https://github.com/OCA/account-invoicing/blob/
# 14.0/sale_line_refund_to_invoice_qty/models/sale.py#L20
# when the tests run in CI of the repo the test fail.
# TODO: The module should be compatible with this case?
# Check Invoiced QTY update after Refund
# for line in sale_order_2.order_line:
# # Check Product line
# if line.product_id.type == "product":
# # self.assertEqual(0.0, line.qty_invoiced)
def test_picking_invoicing_partner_shipping_invoiced(self):
"""
Test the invoice generation grouped by partner/product with 2
picking and 2 moves per picking, but Partner to Shipping is
different from Partner to Invoice.
"""
sale_order_1 = self.env.ref(
"sale_stock_picking_invoicing.main_company-sale_order_1"
)
sale_order_1.action_confirm()
picking = sale_order_1.picking_ids
self.picking_move_state(picking)
sale_order_2 = self.env.ref(
"sale_stock_picking_invoicing.main_company-sale_order_2"
)
sale_order_2.action_confirm()
picking2 = sale_order_2.picking_ids
self.picking_move_state(picking2)
pickings = picking | picking2
invoice = self.create_invoice_wizard(pickings)
# Groupping Invoice
self.assertEqual(len(invoice), 1)
# Invoice should be create with the partner_invoice_id
self.assertEqual(invoice.partner_id, sale_order_1.partner_invoice_id)
# Invoice partner shipping should be the same of picking
self.assertEqual(invoice.partner_shipping_id, picking.partner_id)
self.assertIn(invoice, picking.invoice_ids)
self.assertIn(picking, invoice.picking_ids)
self.assertIn(invoice, picking2.invoice_ids)
self.assertIn(picking2, invoice.picking_ids)
# TODO: Grouping sale line with KEY should be analise
# self.assertEqual(len(invoice.invoice_line_ids), 2)
# 3 Products, 2 Note and 2 Section
self.assertEqual(len(invoice.invoice_line_ids), 7)
for inv_line in invoice.invoice_line_ids.filtered(lambda ln: ln.product_id):
self.assertTrue(inv_line.tax_ids, "Error to map Sale Tax in invoice.line.")
# Post the Invoice to validate the fields
invoice.action_post()
def test_ungrouping_pickings_partner_shipping_different(self):
"""
Test the invoice generation grouped by partner/product with 3
picking and 2 moves per picking, the 3 has the same Partner to
Invoice but one has Partner to Shipping so shouldn't be grouping.
"""
sale_order_1 = self.env.ref(
"sale_stock_picking_invoicing.main_company-sale_order_1"
)
sale_order_1.action_confirm()
picking = sale_order_1.picking_ids
self.picking_move_state(picking)
sale_order_3 = self.env.ref(
"sale_stock_picking_invoicing.main_company-sale_order_3"
)
sale_order_3.action_confirm()
picking3 = sale_order_3.picking_ids
self.picking_move_state(picking3)
sale_order_4 = self.env.ref(
"sale_stock_picking_invoicing.main_company-sale_order_4"
)
sale_order_4.action_confirm()
picking4 = sale_order_4.picking_ids
self.picking_move_state(picking4)
pickings = picking | picking3 | picking4
invoices = self.create_invoice_wizard(pickings)
# Even with same Partner Invoice if the Partner Shipping
# are different should not be Groupping
self.assertEqual(len(invoices), 2)
# Invoice that has different Partner Shipping
# should be not groupping
invoice_pick_1 = invoices.filtered(
lambda t: t.partner_id != t.partner_shipping_id
)
# Invoice should be create with partner_invoice_id
self.assertEqual(invoice_pick_1.partner_id, sale_order_1.partner_invoice_id)
# Invoice create with Partner Shipping used in Picking
self.assertEqual(invoice_pick_1.partner_shipping_id, picking.partner_id)
# Groupping Invoice
invoice_pick_3_4 = invoices.filtered(
lambda t: t.partner_id == t.partner_shipping_id
)
self.assertIn(invoice_pick_3_4, picking3.invoice_ids)
self.assertIn(invoice_pick_3_4, picking4.invoice_ids)
def test_down_payment(self):
"""Test the case with Down Payment"""
sale_order_1 = self.env.ref(
"sale_stock_picking_invoicing.main_company-sale_order_1"
)
sale_order_1.action_confirm()
# Create Invoice Sale
context = {
"active_model": "sale.order",
"active_id": sale_order_1.id,
"active_ids": sale_order_1.ids,
}
# DownPayment
payment_wizard = (
self.env["sale.advance.payment.inv"]
.with_context(**context)
.create(
{
"advance_payment_method": "percentage",
"amount": 50,
}
)
)
payment_wizard.create_invoices()
invoice_down_payment = sale_order_1.invoice_ids[0]
invoice_down_payment.action_post()
payment_register = Form(
self.env["account.payment.register"].with_context(
active_model="account.move",
active_ids=invoice_down_payment.ids,
)
)
journal_cash = self.env["account.journal"].search(
[
("type", "=", "cash"),
("company_id", "=", invoice_down_payment.company_id.id),
],
limit=1,
)
payment_register.journal_id = journal_cash
payment_register.amount = invoice_down_payment.amount_total
payment_register.save()._create_payments()
picking = sale_order_1.picking_ids
self.picking_move_state(picking)
invoice = self.create_invoice_wizard(picking)
# 2 Products, 2 Down Payment, 1 Note and 1 Section
self.assertEqual(len(invoice.invoice_line_ids), 6)
line_section = invoice.invoice_line_ids.filtered(
lambda line: line.display_type == "line_section"
)
assert line_section, "Invoice without Line Section for Down Payment."
down_payment_line = invoice.invoice_line_ids.filtered(
lambda line: line.sale_line_ids.is_downpayment
)
assert down_payment_line, "Invoice without Down Payment line."
def test_default_value_sale_invoicing_policy(self):
"""Test default value for sale_invoicing_policy"""
company = self.env["res.company"].create(
{
"name": "Test",
}
)
self.assertEqual(company.sale_invoicing_policy, "sale_order")
def test_picking_invocing_without_sale_order(self):
"""Test Picking Invoicing without Sale Order"""
picking = self.env.ref("stock_picking_invoicing.stock_picking_invoicing_1")
self.picking_move_state(picking)
invoice = self.create_invoice_wizard(picking)
self.assertEqual(len(invoice), 1)

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2022-TODAY Akretion (http://www.akretion.com/)
@author: Magno Costa <magno.costa@akretion.com.br>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="sale_stock_picking_invocing_res_company_form" model="ir.ui.view">
<field name="name">sale_stock_picking_invocing.res.company.form</field>
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form" />
<field name="arch" type="xml">
<field name="vat" position="after">
<field name="sale_invoicing_policy" />
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2022-TODAY Akretion (http://www.akretion.com/)
@author: Magno Costa <magno.costa@akretion.com.br>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record
id="sale_stock_picking_invoicing_res_config_settings_form"
model="ir.ui.view"
>
<field name="name">sale_stock_picking_invoicing.res.config.settings.form</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="sale.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[@id='sales_settings_invoicing_policy']" position="after">
<div
id="sales_settings_create_invoice_policy"
class="col-12 col-lg-6 o_setting_box"
title="This default value is applied to creation of Invoice."
>
<div class="o_setting_right_pane">
<label for="sale_invoicing_policy" />
<div class="text-muted">
Define if Invoice should be created from Sale Order or from Stock Picking
</div>
<div class="content-group">
<div class="mt16">
<field
name="sale_invoicing_policy"
class="o_light_label"
widget="radio"
/>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1 @@
from . import stock_invoice_onshipping

View file

@ -0,0 +1,359 @@
# Copyright (C) 2020-TODAY KMEE
# Copyright (C) 2021-TODAY Akretion
# @author Magno Costa <magno.costa@akretion.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class StockInvoiceOnshipping(models.TransientModel):
_inherit = "stock.invoice.onshipping"
@api.model
def _default_has_down_payment(self):
pickings = self._load_pickings()
sale_pickings = pickings.filtered(lambda pk: pk.sale_id)
downpayment_lines = False
if sale_pickings:
for pick in sale_pickings:
# order = pick.sale_id
# sale_lines = order.mapped("order_line")
if pick.sale_id.order_line.filtered(lambda ln: ln.is_downpayment):
downpayment_lines = True
return downpayment_lines
deduct_down_payments = fields.Boolean("Deduct down payments", default=True)
has_down_payments = fields.Boolean(
"Has down payments", default=_default_has_down_payment, readonly=True
)
def _get_fields_not_used_from_sale(self):
"""Fields not used from Sale 'prepare' method"""
# For reference, fields get from 'prepare' method
# "ref": self.client_order_ref or ''
# "narration": self.note,
# "campaign_id": self.campaign_id.id,
# "medium_id": self.medium_id.id,
# "source_id": self.source_id.id,
# "team_id": self.team_id.id,
# "partner_shipping_id": self.partner_shipping_id.id,
# "partner_bank_id": self.company_id.partner_id.bank_ids.
# filtered(lambda bank: bank.company_id.id in
# (self.company_id.id, False))[:1].id,
# "invoice_payment_term_id": self.payment_term_id.id,
# "payment_reference": self.reference,
# "transaction_ids": [(6, 0, self.transaction_ids.ids)],
return {
"move_type",
"currency_id",
"user_id",
"invoice_user_id",
"partner_id",
"fiscal_position_id",
"journal_id", # company comes from the journal
"invoice_origin",
"invoice_line_ids",
"company_id",
# Another fields
"__last_update",
"display_name",
}
def _build_invoice_values_from_pickings(self, pickings):
invoice, values = super()._build_invoice_values_from_pickings(pickings)
sale_pickings = pickings.filtered(lambda pk: pk.sale_id)
# Refund case don't get values from Sale Dict
# TODO: Should get any value?
if sale_pickings and self._get_invoice_type() != "out_refund":
# Case more than one Sale Order the fields below will be join
# the others will be overwritting, as done in sale module,
# one more field include here Note/Narration
payment_refs = set()
refs = set()
# Include Narration
narration = set()
for pick in sale_pickings.sorted(key=lambda p: p.name):
# Other modules can included new fields in Sale Order and include
# this fields in the dict of creation Invoice from sale, for
# example:
# - account_payment_sale
# https://github.com/OCA/bank-payment/blob/14.0/
# account_payment_sale/models/sale_order.py#L41
# - sale_commssion
# https://github.com/OCA/commission/blob/14.0/
# sale_commission/models/sale_order.py#L64
# To avoid the necessity of a 'glue' module the method get the
# values from _prepare_invoice but removed some fields of the
# original method, given priority for values from
# stock_picking_invoicing dict, for now it's seems the best to
# way to avoid the 'glue' modules problem.
sale_values = pick.sale_id._prepare_invoice()
# Fields to Join
# origins.add(sale_values["invoice_origin"])
payment_refs.add(sale_values["payment_reference"])
refs.add(sale_values["ref"])
narration.add(sale_values["narration"])
sale_values_rm = {
k: sale_values[k]
for k in set(sale_values) - self._get_fields_not_used_from_sale()
}
values.update(sale_values_rm)
# Fields to join
if len(sale_pickings) > 1:
values.update(
{
"ref": ", ".join(refs)[:2000],
# In this case Origin get Pickings Names
# "invoice_origin": ", ".join(origins),
"payment_reference": len(payment_refs) == 1
and payment_refs.pop()
or False,
"narration": ", ".join(narration),
}
)
return invoice, values
# Check the comment below
# def _get_picking_key(self, picking):
# key = super()._get_picking_key(picking)
# if picking.sale_id:
# key = key + (
# picking.sale_id.payment_term_id,
# picking.sale_id.fiscal_position_id,
# picking.sale_id.commitment_date,
# picking.sale_id.analytic_account_id,
# picking.sale_id.pricelist_id,
# picking.sale_id.company_id,
# )
# return key
def _get_move_key(self, move):
key = super()._get_move_key(move)
if move.sale_line_id:
# TODO: Analise if Sale Lines should be grouped.
# For now remains a problem https://github.com/odoo/odoo/pull/77195
# with field qty_invoiced at sale.order.line, when a invoice line
# has more than one sale line related the field show the total QTY
# of those lines, e.g:
# product_uom_qty | qty_invoiced
# 2.0 | 4.0
# key = key + (
# move.sale_line_id.price_unit,
# move.sale_line_id.customer_lead,
# move.sale_line_id.currency_id,
# move.sale_line_id.tax_id,
# move.sale_line_id.analytic_tag_ids,
# )
key = key + (move.sale_line_id,)
return key
def _get_fields_not_used_from_sale_line(self):
"""Fields not used from Sale Line 'prepare' method"""
# Original fields from sale module
# Fields do get
# "sequence": self.sequence,
# "discount": self.discount,
# "display_type": self.display_type or 'product'
# "is_downpayment": self.is_downpayment
# * optional_values
# * 'N field' included in _prepare_invoice_line method
# by another module
return {
# Fields from Move has priority
"name",
"product_id",
"product_uom_id",
"quantity",
"price_unit",
"tax_ids",
# Already get
"sale_line_ids",
"anlytic_distribution",
# another fields
"__last_update",
"display_name",
}
def _get_invoice_line_values(self, moves, invoice_values, invoice):
values = super()._get_invoice_line_values(moves, invoice_values, invoice)
move = fields.first(moves)
if move.sale_line_id:
# Same make above, get fields informed in Sale Line dict
sale_line_values = move.sale_line_id._prepare_invoice_line()
# Vals informed in any case
values["sale_line_ids"] = [(6, 0, moves.sale_line_id.ids)]
values["analytic_distribution"] = sale_line_values.get(
"analytic_distribution"
)
# Refund case don't get values from Sale Line Dict
# TODO: Should get any value?
if self._get_invoice_type() != "out_refund":
# Same make above, get fields informed in Sale Line dict
sale_line_values = move.sale_line_id._prepare_invoice_line()
sale_line_values_rm = {
k: sale_line_values[k]
for k in set(sale_line_values)
- self._get_fields_not_used_from_sale_line()
}
values.update(sale_line_values_rm)
return values
def _get_pickings_with_sale(self, invoice_values):
pickings = self._load_pickings()
# Filter Picking with Sales
picking_in_invoice_values = self.env["stock.picking"]
for line in invoice_values.get("invoice_line_ids"):
if line[2]:
if len(line[2].get("move_line_ids")[0]) == 2:
# [(4, 233)],
move_line_id = line[2].get("move_line_ids")[0][1]
else:
# [(<Command.SET: 6>, 0, [51])]
move_line_id = line[2].get("move_line_ids")[0][2]
move_line = self.env["stock.move"].browse(move_line_id)
picking_in_invoice_values |= move_line.mapped("picking_id")
sale_pickings = pickings.filtered(
lambda pk: pk.sale_id
# Check Sales Ungrouped
and pk.id in picking_in_invoice_values.ids
)
return sale_pickings
def _create_invoice(self, invoice_values):
"""Override this method if you need to change any values of the
invoice and the lines before the invoice creation
:param invoice_values: dict with the invoice and its lines
:return: invoice
"""
sale_pickings = self._get_pickings_with_sale(invoice_values)
# Refund case don't included Section, Note or DownPayments
if not sale_pickings or self._get_invoice_type() == "out_refund":
return super()._create_invoice(invoice_values)
# Check Other Sale Lines
# Section, Note and Down Payments
section_note_lines = down_payment_lines = self.env["sale.order.line"]
# Resequencing
invoice_item_sequence = (
0 # Incremental sequencing to keep the lines order on the invoice.
)
invoice_item_seq_dict = {}
for pick in sale_pickings.sorted(key=lambda p: p.name):
order = pick.sale_id.with_company(pick.sale_id.company_id)
invoiceable_lines = order._get_invoiceable_lines(final=True)
section_note_lines |= invoiceable_lines.filtered(
lambda ln: ln.display_type in ("line_section", "line_note")
)
down_payment_lines |= invoiceable_lines.filtered(
lambda ln: ln.is_downpayment
)
# Use for Resequencing
for line in order.order_line:
invoice_item_seq_dict[line.id] = invoice_item_sequence
invoice_item_sequence += 1
# Sections and Notes
if section_note_lines:
section_note_vals = []
for line in section_note_lines:
sale_line_vals = line._prepare_invoice_line()
# Change [(4, 59)] for [(6, 0, [59])] to avoid error
# in method to Resequencing
sale_line_vals["sale_line_ids"] = [
(6, 0, [sale_line_vals.get("sale_line_ids")[0][1]])
]
section_note_vals.append((0, 0, sale_line_vals))
invoice_values["invoice_line_ids"] += section_note_vals
# Resequencing, necessary in the case of Grouping Sale Orders
for line in invoice_values.get("invoice_line_ids"):
# [(6, 0, {})]
if line[2]:
sale_line = line[2].get("sale_line_ids")
if sale_line:
# [(6, 0, [58])]
line[2]["sequence"] = invoice_item_seq_dict.get(sale_line[0][2][0])
# Down Payments
# After the Resequencing to put it in the end of Invoice
if down_payment_lines:
down_payment_vals = []
down_payment_section_added = False
for line in down_payment_lines:
if not down_payment_section_added and line.is_downpayment:
# Create a dedicated section for the down payments
# (put at the end of the invoiceable_lines)
down_payment_vals.append(
(
0,
0,
line.order_id._prepare_down_payment_section_line(
sequence=invoice_item_sequence,
),
),
)
down_payment_section_added = True
invoice_item_sequence += 1
if line.is_downpayment:
down_payment_vals.append(
(
0,
0,
line._prepare_invoice_line(
sequence=invoice_item_sequence,
),
),
)
invoice_item_sequence += 1
invoice_values["invoice_line_ids"] += down_payment_vals
moves = (
self.env["account.move"]
.sudo()
.with_context(default_move_type="out_invoice")
.create(invoice_values)
)
# param Final: if True, refunds will be generated if necessary
final = self.deduct_down_payments
if final:
moves.sudo().filtered(
lambda m: m.amount_total < 0
).action_switch_invoice_into_refund_credit_note()
for move in moves:
move.message_post_with_view(
"mail.message_origin_link",
# In this case the Origin are Pickings
# values={
# "self": move,
# "origin": move.line_ids.mapped("sale_line_ids.order_id"),
# },
values={
"self": move.picking_ids,
"origin": move.picking_ids,
},
subtype_id=self.env.ref("mail.mt_note").id,
)
return moves

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_sale_stock_invoice_onshipping" model="ir.ui.view">
<field name="name">Sale Stock Invoice Onshipping</field>
<field name="model">stock.invoice.onshipping</field>
<field
name="inherit_id"
ref="stock_picking_invoicing.view_stock_invoice_onshipping"
/>
<field name="arch" type="xml">
<field name="group" position="after">
<field name="has_down_payments" invisible="1" />
<label
for="deduct_down_payments"
string=""
attrs="{'invisible': [('has_down_payments', '=', False)]}"
/>
<div
attrs="{'invisible': [('has_down_payments', '=', False)]}"
id="down_payment_details"
>
<field name="deduct_down_payments" nolabel="1" />
<label for="deduct_down_payments" />
</div>
</field>
</field>
</record>
</odoo>