lambda-erp 1.0.2__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/PKG-INFO +1 -1
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/pdf_profiles.py +6 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/masters.py +5 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/services.py +3 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/pos_invoice.py +42 -17
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/purchase_invoice.py +31 -15
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/sales_invoice.py +37 -22
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/buying/purchase_order.py +11 -15
- lambda_erp-1.1.0/lambda_erp/controllers/defaults.py +114 -0
- lambda_erp-1.1.0/lambda_erp/controllers/item_price.py +247 -0
- lambda_erp-1.1.0/lambda_erp/controllers/pricing_rule.py +273 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/database.py +226 -12
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/selling/quotation.py +15 -15
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/selling/sales_order.py +12 -16
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/pyproject.toml +1 -1
- lambda_erp-1.0.2/lambda_erp/controllers/defaults.py +0 -51
- lambda_erp-1.0.2/lambda_erp/controllers/pricing_rule.py +0 -112
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/.gitignore +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/LICENSE +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/README.md +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/__init__.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/attachments.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/auth.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/bootstrap.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/chat.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/demo_limits.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/deps.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/errors.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/list_values.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/main.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/oauth.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/pdf.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/pdf_contract.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/pdf_exports.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/providers.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/remarks_md.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/__init__.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/accounting.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/actions.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/admin.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/analytics.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/availability.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/bank_reconciliation.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/bank_statements.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/chat_api.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/documents.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/mcp.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/proposals.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/reports.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/routers/setup.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/templates/_proposal_totals.html +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/templates/_record_base.html +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/templates/document.html +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/templates/proposal.html +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/templates/record.html +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/time_filters.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/api/tool_permissions.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/docs/agents/README.md +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/frontend/LICENSE +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/frontend/README.md +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/frontend/src/api/client.ts +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/__init__.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/__init__.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/bank_account.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/bank_reconciliation.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/bank_statement_import.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/bank_transaction.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/budget.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/camt.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/chart_of_accounts.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/general_ledger.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/journal_entry.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/payment_entry.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/revaluation.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/settlement.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/setup/__init__.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/setup/engine.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/setup/pack.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/setup/packs/ch.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/setup/packs/generic.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/setup/profiles.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/setup/spine.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/accounting/subscription.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/assets/__init__.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/assets/asset.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/assets/lifecycle.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/assets/reservation.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/buying/__init__.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/controllers/__init__.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/controllers/currency.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/controllers/item_prices.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/controllers/taxes_and_totals.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/exceptions.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/hooks.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/master_integrity.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/model.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/selling/__init__.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/selling/proposal.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/simulation.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/stock/__init__.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/stock/delivery_note.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/stock/purchase_receipt.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/stock/stock_entry.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/stock/stock_ledger.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/timestamps.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/utils.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/validation.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/lambda_erp/workflow.py +0 -0
- {lambda_erp-1.0.2 → lambda_erp-1.1.0}/terraform/README.md +0 -0
|
@@ -88,6 +88,12 @@ PROFILES.update({
|
|
|
88
88
|
tables=(PDFTable('monthly_distribution', ('month', 'percentage'), ('month', 'percentage')),)),
|
|
89
89
|
'Pricing Rule': PDFProfile('record', 'Pricing rule overview',
|
|
90
90
|
fields=('title', 'company', 'item_code', 'selling', 'buying', 'rate_or_discount', 'rate', 'discount_percentage', 'discount_amount', 'min_qty', 'valid_from', 'valid_upto', 'priority', 'enabled'), required=('title', 'rate_or_discount')),
|
|
91
|
+
'Price List': PDFProfile('record', 'Price list overview',
|
|
92
|
+
fields=('price_list_name', 'currency', 'selling', 'buying', 'enabled'),
|
|
93
|
+
required=('price_list_name', 'currency')),
|
|
94
|
+
'Item Price': PDFProfile('record', 'Item price overview',
|
|
95
|
+
fields=('item_code', 'price_list', 'customer', 'supplier', 'uom', 'min_qty', 'rate', 'valid_from', 'valid_upto', 'enabled'),
|
|
96
|
+
required=('item_code', 'price_list', 'rate')),
|
|
91
97
|
'Bank Account': PDFProfile('record', 'Bank account overview',
|
|
92
98
|
fields=('account_name', 'company', 'account', 'iban', 'currency', 'bank_name', 'bic', 'disabled'), required=('account_name', 'company', 'account', 'iban', 'currency')),
|
|
93
99
|
'Bank Transaction': PDFProfile('record', 'Bank transaction overview',
|
|
@@ -67,6 +67,8 @@ DELETE_REFERENCE_CHECKS = {
|
|
|
67
67
|
('SELECT 1 FROM "POS Invoice" WHERE customer = ? LIMIT 1', "POS invoice"),
|
|
68
68
|
('SELECT 1 FROM "Payment Entry" WHERE party_type = \'Customer\' AND party = ? LIMIT 1', "payment entry"),
|
|
69
69
|
('SELECT 1 FROM "Subscription" WHERE party_type = \'Customer\' AND party = ? LIMIT 1', "subscription"),
|
|
70
|
+
('SELECT 1 FROM "Item Price" WHERE customer = ? LIMIT 1', "item price"),
|
|
71
|
+
('SELECT 1 FROM "Pricing Rule" WHERE customer = ? LIMIT 1', "pricing rule"),
|
|
70
72
|
],
|
|
71
73
|
"supplier": [
|
|
72
74
|
('SELECT 1 FROM "Purchase Order" WHERE supplier = ? LIMIT 1', "purchase order"),
|
|
@@ -88,6 +90,7 @@ DELETE_REFERENCE_CHECKS = {
|
|
|
88
90
|
('SELECT 1 FROM "Stock Ledger Entry" WHERE item_code = ? LIMIT 1', "stock ledger entry"),
|
|
89
91
|
('SELECT 1 FROM "Bin" WHERE item_code = ? LIMIT 1', "bin"),
|
|
90
92
|
('SELECT 1 FROM "Pricing Rule" WHERE item_code = ? LIMIT 1', "pricing rule"),
|
|
93
|
+
('SELECT 1 FROM "Item Price" WHERE item_code = ? LIMIT 1', "item price"),
|
|
91
94
|
('SELECT 1 FROM "Subscription Plan" WHERE item_code = ? LIMIT 1', "subscription plan"),
|
|
92
95
|
('SELECT 1 FROM "Asset" WHERE item_code = ? LIMIT 1', "asset"),
|
|
93
96
|
('SELECT 1 FROM "Reservation" WHERE item_code = ? LIMIT 1', "reservation"),
|
|
@@ -213,6 +216,8 @@ def _normalize_master_data(data: dict) -> dict:
|
|
|
213
216
|
|
|
214
217
|
|
|
215
218
|
MASTER_LINK_FIELDS = {
|
|
219
|
+
'customer': {'default_price_list': 'Price List'},
|
|
220
|
+
'company': {'default_price_list': 'Price List'},
|
|
216
221
|
'item': {'default_warehouse': 'Warehouse'},
|
|
217
222
|
'warehouse': {'company': 'Company', 'account': 'Account', 'parent_warehouse': 'Warehouse'},
|
|
218
223
|
'account': {'company': 'Company', 'parent_account': 'Account'},
|
|
@@ -23,6 +23,7 @@ from lambda_erp.stock.delivery_note import DeliveryNote, make_delivery_note, mak
|
|
|
23
23
|
from lambda_erp.stock.purchase_receipt import PurchaseReceipt, make_purchase_receipt, make_purchase_receipt_return
|
|
24
24
|
from lambda_erp.accounting.pos_invoice import POSInvoice
|
|
25
25
|
from lambda_erp.controllers.pricing_rule import PricingRule
|
|
26
|
+
from lambda_erp.controllers.item_price import PriceList, ItemPrice
|
|
26
27
|
from lambda_erp.accounting.budget import Budget
|
|
27
28
|
from lambda_erp.accounting.subscription import Subscription
|
|
28
29
|
from lambda_erp.accounting.bank_transaction import BankTransaction
|
|
@@ -47,6 +48,8 @@ DOCUMENT_CLASSES = {
|
|
|
47
48
|
"Purchase Receipt": PurchaseReceipt,
|
|
48
49
|
"POS Invoice": POSInvoice,
|
|
49
50
|
"Pricing Rule": PricingRule,
|
|
51
|
+
"Price List": PriceList,
|
|
52
|
+
"Item Price": ItemPrice,
|
|
50
53
|
"Budget": Budget,
|
|
51
54
|
"Subscription": Subscription,
|
|
52
55
|
"Bank Transaction": BankTransaction,
|
|
@@ -13,7 +13,11 @@ from lambda_erp.model import Document
|
|
|
13
13
|
from lambda_erp.utils import _dict, flt, nowdate
|
|
14
14
|
from lambda_erp.database import get_db
|
|
15
15
|
from lambda_erp.controllers.taxes_and_totals import calculate_taxes_and_totals
|
|
16
|
-
from lambda_erp.controllers.defaults import
|
|
16
|
+
from lambda_erp.controllers.defaults import (
|
|
17
|
+
set_default_currency, apply_external_source_defaults,
|
|
18
|
+
derived_pricing_fields, derived_line_pricing_fields,
|
|
19
|
+
)
|
|
20
|
+
from lambda_erp.controllers.item_price import set_item_defaults
|
|
17
21
|
from lambda_erp.exceptions import ValidationError
|
|
18
22
|
from lambda_erp.stock.stock_ledger import (
|
|
19
23
|
make_sl_entries,
|
|
@@ -65,16 +69,44 @@ class POSInvoice(Document):
|
|
|
65
69
|
if not self.posting_date:
|
|
66
70
|
self.posting_date = nowdate()
|
|
67
71
|
|
|
72
|
+
apply_external_source_defaults(self)
|
|
68
73
|
self._set_customer_name()
|
|
69
74
|
self._set_missing_accounts()
|
|
75
|
+
set_default_currency(self, "Customer", "customer")
|
|
70
76
|
self._set_item_defaults()
|
|
77
|
+
self._validate_external_source_stock()
|
|
71
78
|
if self.is_return:
|
|
72
79
|
self._validate_return()
|
|
73
|
-
|
|
80
|
+
# POS was the only selling document that never applied pricing rules.
|
|
81
|
+
# Nothing about a till sale makes a discount inapplicable, so the same
|
|
82
|
+
# item rang up at a different price depending on which document the
|
|
83
|
+
# sale happened to be written to.
|
|
84
|
+
from lambda_erp.controllers.pricing_rule import apply_pricing_rules
|
|
85
|
+
apply_pricing_rules(self)
|
|
86
|
+
|
|
74
87
|
calculate_taxes_and_totals(self)
|
|
75
88
|
self._calculate_payments()
|
|
76
89
|
self._set_status()
|
|
77
90
|
|
|
91
|
+
def _validate_external_source_stock(self):
|
|
92
|
+
"""An upstream system's stock movement already accounts for the goods.
|
|
93
|
+
|
|
94
|
+
`_validate_no_double_shipment` catches the same mistake on the Sales
|
|
95
|
+
Order -> Delivery Note path by looking up `sales_order_item`. A document
|
|
96
|
+
created from an external system has no such link, so that lookup finds
|
|
97
|
+
nothing and passes. Without this guard the upstream issue and this
|
|
98
|
+
document's own update_stock each decrement the same quantity.
|
|
99
|
+
"""
|
|
100
|
+
if not self._data.get("external_source"):
|
|
101
|
+
return
|
|
102
|
+
if flt(self.get("update_stock")):
|
|
103
|
+
raise ValidationError(
|
|
104
|
+
f"Cannot submit with update_stock=1: this document is sourced "
|
|
105
|
+
f"from {self._data['external_source']}, which already recorded "
|
|
106
|
+
f"the stock movement. Use this invoice as a bill only and "
|
|
107
|
+
f"record any required stock movement in a separate stock document."
|
|
108
|
+
)
|
|
109
|
+
|
|
78
110
|
def _validate_return(self):
|
|
79
111
|
from lambda_erp.workflow import validate_return
|
|
80
112
|
validate_return(self)
|
|
@@ -100,18 +132,8 @@ class POSInvoice(Document):
|
|
|
100
132
|
item["cost_center"] = default_cc
|
|
101
133
|
|
|
102
134
|
def _set_item_defaults(self):
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if item.get("item_code") and not item.get("item_name"):
|
|
106
|
-
item_data = db.get_value(
|
|
107
|
-
"Item", item["item_code"],
|
|
108
|
-
["item_name", "description", "stock_uom", "standard_rate"]
|
|
109
|
-
)
|
|
110
|
-
if item_data:
|
|
111
|
-
item["item_name"] = item_data.item_name
|
|
112
|
-
item["uom"] = item.get("uom") or item_data.stock_uom
|
|
113
|
-
if item.get("rate") is None and item.get("price_list_rate") is None:
|
|
114
|
-
item["rate"] = flt(item_data.standard_rate)
|
|
135
|
+
"""Names, units and unsupplied rates. See controllers/item_price.py."""
|
|
136
|
+
set_item_defaults(self, "Customer", "customer", set_description=False)
|
|
115
137
|
|
|
116
138
|
def _calculate_payments(self):
|
|
117
139
|
paid = sum(flt(p.get("amount", 0)) for p in self.get("payments") or [])
|
|
@@ -197,16 +219,17 @@ class POSInvoice(Document):
|
|
|
197
219
|
account = item.get("income_account")
|
|
198
220
|
if not account:
|
|
199
221
|
continue
|
|
200
|
-
|
|
222
|
+
key = (account, item.get("cost_center"))
|
|
223
|
+
income_accounts[key] = income_accounts.get(key, 0) + flt(item.get("net_amount", 0))
|
|
201
224
|
|
|
202
|
-
for account, amount in income_accounts.items():
|
|
225
|
+
for (account, cost_center), amount in income_accounts.items():
|
|
203
226
|
gl_entries.append(_dict(
|
|
204
227
|
account=account,
|
|
205
228
|
credit=flt(amount, 2),
|
|
206
229
|
credit_in_account_currency=flt(amount, 2),
|
|
207
230
|
debit=0,
|
|
208
231
|
debit_in_account_currency=0,
|
|
209
|
-
cost_center=
|
|
232
|
+
cost_center=cost_center,
|
|
210
233
|
voucher_type=self.DOCTYPE,
|
|
211
234
|
voucher_no=self.name,
|
|
212
235
|
posting_date=self.posting_date,
|
|
@@ -288,6 +311,7 @@ def make_pos_return(posi_name):
|
|
|
288
311
|
raise ValidationError("Cannot create a return against a return")
|
|
289
312
|
|
|
290
313
|
return_pos = POSInvoice(
|
|
314
|
+
**derived_pricing_fields(original, is_return=True),
|
|
291
315
|
customer=original.customer,
|
|
292
316
|
company=original.company,
|
|
293
317
|
currency=original.get("currency") or "USD",
|
|
@@ -302,6 +326,7 @@ def make_pos_return(posi_name):
|
|
|
302
326
|
from lambda_erp.workflow import returnable_rows
|
|
303
327
|
for item in returnable_rows(original):
|
|
304
328
|
return_pos.append("items", _dict(
|
|
329
|
+
**derived_line_pricing_fields(item),
|
|
305
330
|
item_code=item.get("item_code"),
|
|
306
331
|
item_name=item.get("item_name"),
|
|
307
332
|
description=item.get("description"),
|
|
@@ -14,7 +14,11 @@ from lambda_erp.model import Document
|
|
|
14
14
|
from lambda_erp.utils import _dict, flt, nowdate, add_days
|
|
15
15
|
from lambda_erp.database import get_db
|
|
16
16
|
from lambda_erp.controllers.taxes_and_totals import calculate_taxes_and_totals
|
|
17
|
-
from lambda_erp.controllers.defaults import
|
|
17
|
+
from lambda_erp.controllers.defaults import (
|
|
18
|
+
set_default_currency, apply_external_source_defaults,
|
|
19
|
+
derived_pricing_fields, derived_line_pricing_fields,
|
|
20
|
+
)
|
|
21
|
+
from lambda_erp.controllers.item_price import set_item_defaults
|
|
18
22
|
from lambda_erp.accounting.general_ledger import make_gl_entries, make_reverse_gl_entries, to_base_currency
|
|
19
23
|
from lambda_erp.stock.stock_ledger import (
|
|
20
24
|
make_sl_entries,
|
|
@@ -65,11 +69,14 @@ class PurchaseInvoice(Document):
|
|
|
65
69
|
if not self.posting_date:
|
|
66
70
|
self.posting_date = nowdate()
|
|
67
71
|
|
|
72
|
+
apply_external_source_defaults(self)
|
|
68
73
|
self._set_supplier_name()
|
|
69
74
|
self._set_missing_accounts()
|
|
75
|
+
set_default_currency(self, "Supplier", "supplier")
|
|
70
76
|
self._set_item_defaults()
|
|
71
77
|
self._validate_stock_warehouses()
|
|
72
78
|
self._validate_no_double_receipt()
|
|
79
|
+
self._validate_external_source_stock()
|
|
73
80
|
|
|
74
81
|
if self.is_return:
|
|
75
82
|
self._validate_return()
|
|
@@ -77,7 +84,6 @@ class PurchaseInvoice(Document):
|
|
|
77
84
|
from lambda_erp.controllers.pricing_rule import apply_pricing_rules
|
|
78
85
|
apply_pricing_rules(self)
|
|
79
86
|
|
|
80
|
-
set_default_currency(self, "Supplier", "supplier")
|
|
81
87
|
|
|
82
88
|
calculate_taxes_and_totals(self)
|
|
83
89
|
|
|
@@ -172,6 +178,25 @@ class PurchaseInvoice(Document):
|
|
|
172
178
|
f"is already allocated against it. Cancel the Payment Entry first."
|
|
173
179
|
)
|
|
174
180
|
|
|
181
|
+
def _validate_external_source_stock(self):
|
|
182
|
+
"""An upstream system's stock movement already accounts for the goods.
|
|
183
|
+
|
|
184
|
+
`_validate_no_double_shipment` catches the same mistake on the Sales
|
|
185
|
+
Order -> Delivery Note path by looking up `sales_order_item`. A document
|
|
186
|
+
created from an external system has no such link, so that lookup finds
|
|
187
|
+
nothing and passes. Without this guard the upstream issue and this
|
|
188
|
+
document's own update_stock each decrement the same quantity.
|
|
189
|
+
"""
|
|
190
|
+
if not self._data.get("external_source"):
|
|
191
|
+
return
|
|
192
|
+
if flt(self.get("update_stock")):
|
|
193
|
+
raise ValidationError(
|
|
194
|
+
f"Cannot submit with update_stock=1: this document is sourced "
|
|
195
|
+
f"from {self._data['external_source']}, which already recorded "
|
|
196
|
+
f"the stock movement. Use this invoice as a bill only and "
|
|
197
|
+
f"record any required stock movement in a separate stock document."
|
|
198
|
+
)
|
|
199
|
+
|
|
175
200
|
def _validate_no_double_receipt(self):
|
|
176
201
|
"""Block update_stock=1 when the referenced Purchase Order already has
|
|
177
202
|
a Purchase Receipt for the line. Otherwise stock arrives twice: once
|
|
@@ -202,19 +227,8 @@ class PurchaseInvoice(Document):
|
|
|
202
227
|
)
|
|
203
228
|
|
|
204
229
|
def _set_item_defaults(self):
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if item.get("item_code") and not item.get("item_name"):
|
|
208
|
-
item_data = db.get_value(
|
|
209
|
-
"Item", item["item_code"],
|
|
210
|
-
["item_name", "description", "stock_uom", "standard_rate"]
|
|
211
|
-
)
|
|
212
|
-
if item_data:
|
|
213
|
-
item["item_name"] = item_data.item_name
|
|
214
|
-
item["description"] = item.get("description") or item_data.description
|
|
215
|
-
item["uom"] = item.get("uom") or item_data.stock_uom
|
|
216
|
-
if item.get("rate") is None and item.get("price_list_rate") is None:
|
|
217
|
-
item["rate"] = flt(item_data.standard_rate)
|
|
230
|
+
"""Names, units and unsupplied rates. See controllers/item_price.py."""
|
|
231
|
+
set_item_defaults(self, "Supplier", "supplier")
|
|
218
232
|
|
|
219
233
|
def _validate_return(self):
|
|
220
234
|
from lambda_erp.workflow import validate_return
|
|
@@ -408,6 +422,7 @@ def make_purchase_return(pinv_name):
|
|
|
408
422
|
raise ValidationError("Cannot create a return against a return")
|
|
409
423
|
|
|
410
424
|
return_inv = PurchaseInvoice(
|
|
425
|
+
**derived_pricing_fields(original, is_return=True),
|
|
411
426
|
supplier=original.supplier,
|
|
412
427
|
company=original.company,
|
|
413
428
|
currency=original.get("currency") or "USD",
|
|
@@ -425,6 +440,7 @@ def make_purchase_return(pinv_name):
|
|
|
425
440
|
from lambda_erp.workflow import returnable_rows
|
|
426
441
|
for item in returnable_rows(original):
|
|
427
442
|
return_inv.append("items", _dict(
|
|
443
|
+
**derived_line_pricing_fields(item),
|
|
428
444
|
item_code=item.get("item_code"),
|
|
429
445
|
item_name=item.get("item_name"),
|
|
430
446
|
description=item.get("description"),
|
|
@@ -18,7 +18,11 @@ from lambda_erp.model import Document
|
|
|
18
18
|
from lambda_erp.utils import _dict, flt, getdate, nowdate, add_days
|
|
19
19
|
from lambda_erp.database import get_db
|
|
20
20
|
from lambda_erp.controllers.taxes_and_totals import calculate_taxes_and_totals
|
|
21
|
-
from lambda_erp.controllers.defaults import
|
|
21
|
+
from lambda_erp.controllers.defaults import (
|
|
22
|
+
set_default_currency, apply_external_source_defaults,
|
|
23
|
+
derived_pricing_fields, derived_line_pricing_fields,
|
|
24
|
+
)
|
|
25
|
+
from lambda_erp.controllers.item_price import set_item_defaults
|
|
22
26
|
from lambda_erp.accounting.general_ledger import make_gl_entries, make_reverse_gl_entries, to_base_currency
|
|
23
27
|
from lambda_erp.stock.stock_ledger import (
|
|
24
28
|
make_sl_entries,
|
|
@@ -81,10 +85,13 @@ class SalesInvoice(Document):
|
|
|
81
85
|
if not self.posting_date:
|
|
82
86
|
self.posting_date = nowdate()
|
|
83
87
|
|
|
88
|
+
apply_external_source_defaults(self)
|
|
84
89
|
self._set_customer_name()
|
|
85
90
|
self._set_missing_accounts()
|
|
91
|
+
set_default_currency(self, "Customer", "customer")
|
|
86
92
|
self._set_item_defaults()
|
|
87
93
|
self._validate_no_double_shipment()
|
|
94
|
+
self._validate_external_source_stock()
|
|
88
95
|
|
|
89
96
|
if self.is_return:
|
|
90
97
|
self._validate_return()
|
|
@@ -92,7 +99,6 @@ class SalesInvoice(Document):
|
|
|
92
99
|
from lambda_erp.controllers.pricing_rule import apply_pricing_rules
|
|
93
100
|
apply_pricing_rules(self)
|
|
94
101
|
|
|
95
|
-
set_default_currency(self, "Customer", "customer")
|
|
96
102
|
|
|
97
103
|
calculate_taxes_and_totals(self)
|
|
98
104
|
|
|
@@ -133,19 +139,27 @@ class SalesInvoice(Document):
|
|
|
133
139
|
item["cost_center"] = default_cc
|
|
134
140
|
|
|
135
141
|
def _set_item_defaults(self):
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
142
|
+
"""Names, units and unsupplied rates. See controllers/item_price.py."""
|
|
143
|
+
set_item_defaults(self, "Customer", "customer")
|
|
144
|
+
|
|
145
|
+
def _validate_external_source_stock(self):
|
|
146
|
+
"""An upstream system's stock movement already accounts for the goods.
|
|
147
|
+
|
|
148
|
+
`_validate_no_double_shipment` catches the same mistake on the Sales
|
|
149
|
+
Order -> Delivery Note path by looking up `sales_order_item`. A document
|
|
150
|
+
created from an external system has no such link, so that lookup finds
|
|
151
|
+
nothing and passes. Without this guard the upstream issue and this
|
|
152
|
+
document's own update_stock each decrement the same quantity.
|
|
153
|
+
"""
|
|
154
|
+
if not self._data.get("external_source"):
|
|
155
|
+
return
|
|
156
|
+
if flt(self.get("update_stock")):
|
|
157
|
+
raise ValidationError(
|
|
158
|
+
f"Cannot submit with update_stock=1: this document is sourced "
|
|
159
|
+
f"from {self._data['external_source']}, which already recorded "
|
|
160
|
+
f"the stock movement. Use this invoice as a bill only and "
|
|
161
|
+
f"record any required stock movement in a separate stock document."
|
|
162
|
+
)
|
|
149
163
|
|
|
150
164
|
def _validate_no_double_shipment(self):
|
|
151
165
|
"""Block update_stock=1 when the referenced Sales Order already has a
|
|
@@ -338,15 +352,14 @@ class SalesInvoice(Document):
|
|
|
338
352
|
)
|
|
339
353
|
|
|
340
354
|
# 2. Credit: Income accounts (per item)
|
|
341
|
-
#
|
|
355
|
+
# Group only lines with the same account AND cost centre.
|
|
342
356
|
income_accounts = {}
|
|
343
357
|
for item in self.get("items"):
|
|
344
358
|
account = item.get("income_account")
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
income_accounts[account] += flt(item.get("net_amount", 0))
|
|
359
|
+
key = (account, item.get("cost_center"))
|
|
360
|
+
income_accounts[key] = income_accounts.get(key, 0) + flt(item.get("net_amount", 0))
|
|
348
361
|
|
|
349
|
-
for account, amount in income_accounts.items():
|
|
362
|
+
for (account, cost_center), amount in income_accounts.items():
|
|
350
363
|
gl_entries.append(
|
|
351
364
|
_dict(
|
|
352
365
|
account=account,
|
|
@@ -354,7 +367,7 @@ class SalesInvoice(Document):
|
|
|
354
367
|
credit_in_account_currency=flt(amount, 2),
|
|
355
368
|
debit=0,
|
|
356
369
|
debit_in_account_currency=0,
|
|
357
|
-
cost_center=
|
|
370
|
+
cost_center=cost_center,
|
|
358
371
|
voucher_type=self.DOCTYPE,
|
|
359
372
|
voucher_no=self.name,
|
|
360
373
|
posting_date=self.posting_date,
|
|
@@ -373,7 +386,7 @@ class SalesInvoice(Document):
|
|
|
373
386
|
credit_in_account_currency=flt(tax["tax_amount"], 2),
|
|
374
387
|
debit=0,
|
|
375
388
|
debit_in_account_currency=0,
|
|
376
|
-
cost_center=
|
|
389
|
+
cost_center=get_db().get_value("Company", self.company, "default_cost_center"),
|
|
377
390
|
voucher_type=self.DOCTYPE,
|
|
378
391
|
voucher_no=self.name,
|
|
379
392
|
posting_date=self.posting_date,
|
|
@@ -404,6 +417,7 @@ def make_sales_return(sinv_name):
|
|
|
404
417
|
raise ValidationError("Cannot create a return against a return")
|
|
405
418
|
|
|
406
419
|
return_inv = SalesInvoice(
|
|
420
|
+
**derived_pricing_fields(original, is_return=True),
|
|
407
421
|
customer=original.customer,
|
|
408
422
|
company=original.company,
|
|
409
423
|
currency=original.get("currency") or "USD",
|
|
@@ -421,6 +435,7 @@ def make_sales_return(sinv_name):
|
|
|
421
435
|
from lambda_erp.workflow import returnable_rows
|
|
422
436
|
for item in returnable_rows(original):
|
|
423
437
|
return_inv.append("items", _dict(
|
|
438
|
+
**derived_line_pricing_fields(item),
|
|
424
439
|
item_code=item.get("item_code"),
|
|
425
440
|
item_name=item.get("item_name"),
|
|
426
441
|
description=item.get("description"),
|
|
@@ -14,7 +14,11 @@ from lambda_erp.model import Document
|
|
|
14
14
|
from lambda_erp.utils import _dict, flt, getdate, nowdate
|
|
15
15
|
from lambda_erp.database import get_db
|
|
16
16
|
from lambda_erp.controllers.taxes_and_totals import calculate_taxes_and_totals
|
|
17
|
-
from lambda_erp.controllers.defaults import
|
|
17
|
+
from lambda_erp.controllers.defaults import (
|
|
18
|
+
set_default_currency, apply_external_source_defaults,
|
|
19
|
+
derived_pricing_fields, derived_line_pricing_fields,
|
|
20
|
+
)
|
|
21
|
+
from lambda_erp.controllers.item_price import set_item_defaults
|
|
18
22
|
from lambda_erp.exceptions import ValidationError
|
|
19
23
|
|
|
20
24
|
class PurchaseOrder(Document):
|
|
@@ -49,13 +53,14 @@ class PurchaseOrder(Document):
|
|
|
49
53
|
if not self.transaction_date:
|
|
50
54
|
self.transaction_date = nowdate()
|
|
51
55
|
|
|
56
|
+
apply_external_source_defaults(self)
|
|
52
57
|
self._set_supplier_name()
|
|
58
|
+
set_default_currency(self, "Supplier", "supplier")
|
|
53
59
|
self._set_item_defaults()
|
|
54
60
|
|
|
55
61
|
from lambda_erp.controllers.pricing_rule import apply_pricing_rules
|
|
56
62
|
apply_pricing_rules(self)
|
|
57
63
|
|
|
58
|
-
set_default_currency(self, "Supplier", "supplier")
|
|
59
64
|
|
|
60
65
|
calculate_taxes_and_totals(self)
|
|
61
66
|
|
|
@@ -65,19 +70,8 @@ class PurchaseOrder(Document):
|
|
|
65
70
|
self.supplier_name = db.get_value("Supplier", self.supplier, "supplier_name")
|
|
66
71
|
|
|
67
72
|
def _set_item_defaults(self):
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if item.get("item_code") and not item.get("item_name"):
|
|
71
|
-
item_data = db.get_value(
|
|
72
|
-
"Item", item["item_code"],
|
|
73
|
-
["item_name", "description", "stock_uom", "standard_rate"]
|
|
74
|
-
)
|
|
75
|
-
if item_data:
|
|
76
|
-
item["item_name"] = item_data.item_name
|
|
77
|
-
item["description"] = item.get("description") or item_data.description
|
|
78
|
-
item["uom"] = item.get("uom") or item_data.stock_uom
|
|
79
|
-
if item.get("rate") is None and item.get("price_list_rate") is None:
|
|
80
|
-
item["rate"] = flt(item_data.standard_rate)
|
|
73
|
+
"""Names, units and unsupplied rates. See controllers/item_price.py."""
|
|
74
|
+
set_item_defaults(self, "Supplier", "supplier")
|
|
81
75
|
|
|
82
76
|
def _update_ordered_qty(self, direction=1):
|
|
83
77
|
from lambda_erp.workflow import refresh_order_progress
|
|
@@ -101,6 +95,7 @@ def make_purchase_invoice(purchase_order_name):
|
|
|
101
95
|
raise ValidationError("Purchase Order must be submitted before creating Purchase Invoice")
|
|
102
96
|
|
|
103
97
|
pi = PurchaseInvoice(
|
|
98
|
+
**derived_pricing_fields(po, is_return=False),
|
|
104
99
|
supplier=po.supplier,
|
|
105
100
|
supplier_name=po.supplier_name,
|
|
106
101
|
company=po.company,
|
|
@@ -116,6 +111,7 @@ def make_purchase_invoice(purchase_order_name):
|
|
|
116
111
|
continue
|
|
117
112
|
|
|
118
113
|
pi.append("items", _dict(
|
|
114
|
+
**derived_line_pricing_fields(item),
|
|
119
115
|
item_code=item.get("item_code"),
|
|
120
116
|
item_name=item.get("item_name"),
|
|
121
117
|
description=item.get("description"),
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Shared default-setting helpers for transactional documents."""
|
|
2
|
+
|
|
3
|
+
from lambda_erp.database import get_db
|
|
4
|
+
from lambda_erp.utils import flt
|
|
5
|
+
from lambda_erp.exceptions import ValidationError
|
|
6
|
+
from lambda_erp.controllers.currency import get_exchange_rate
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def set_default_company(doc):
|
|
10
|
+
"""Set company to the first available company if not specified."""
|
|
11
|
+
if doc._data.get("company"):
|
|
12
|
+
return
|
|
13
|
+
db = get_db()
|
|
14
|
+
companies = db.get_all("Company", fields=["name"], limit=1)
|
|
15
|
+
if companies:
|
|
16
|
+
doc._data["company"] = companies[0]["name"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def set_default_currency(doc, party_type=None, party_field=None):
|
|
20
|
+
"""Default a transaction's currency and conversion_rate for new entries.
|
|
21
|
+
|
|
22
|
+
Currency precedence: a value already on the doc -> the party's
|
|
23
|
+
default_currency -> the company's (base/functional) default_currency ->
|
|
24
|
+
"USD".
|
|
25
|
+
|
|
26
|
+
conversion_rate is forced to 1.0 whenever the document currency equals the
|
|
27
|
+
company's base currency. For a foreign currency the caller-supplied rate is
|
|
28
|
+
kept; if none was supplied it is looked up from the Currency Exchange table
|
|
29
|
+
for the document's date. A foreign currency with no rate on file raises
|
|
30
|
+
(via get_exchange_rate) rather than silently booking at 1.0.
|
|
31
|
+
"""
|
|
32
|
+
db = get_db()
|
|
33
|
+
company = doc._data.get("company")
|
|
34
|
+
base_currency = db.get_value("Company", company, "default_currency") if company else None
|
|
35
|
+
|
|
36
|
+
currency = doc._data.get("currency")
|
|
37
|
+
if not currency:
|
|
38
|
+
if party_type and party_field:
|
|
39
|
+
party = doc._data.get(party_field)
|
|
40
|
+
if party:
|
|
41
|
+
currency = db.get_value(party_type, party, "default_currency")
|
|
42
|
+
currency = currency or base_currency or "USD"
|
|
43
|
+
doc._data["currency"] = currency
|
|
44
|
+
|
|
45
|
+
rate = flt(doc._data.get("conversion_rate"))
|
|
46
|
+
if base_currency and currency == base_currency:
|
|
47
|
+
rate = 1.0
|
|
48
|
+
elif rate <= 0:
|
|
49
|
+
# No rate supplied — look one up (carry-forward) for the doc's date.
|
|
50
|
+
doc_date = doc._data.get("posting_date") or doc._data.get("transaction_date")
|
|
51
|
+
rate = get_exchange_rate(currency, base_currency or "USD", doc_date)
|
|
52
|
+
doc._data["conversion_rate"] = rate
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def apply_external_source_defaults(doc):
|
|
56
|
+
"""Safe defaults for a document owned by an upstream system.
|
|
57
|
+
|
|
58
|
+
`external_source` means another system already decided this document's
|
|
59
|
+
content: it computed the rates under its own contract, and if goods moved
|
|
60
|
+
it recorded that movement in its own ledger. So pricing rules are off by
|
|
61
|
+
default here — re-deriving a rate that was already agreed is how an
|
|
62
|
+
integration silently bills a different number than the source system shows.
|
|
63
|
+
|
|
64
|
+
An explicit `ignore_pricing_rule: 0` still wins, for the caller that wants
|
|
65
|
+
upstream identity but local pricing.
|
|
66
|
+
"""
|
|
67
|
+
for field in ("external_source", "external_reference"):
|
|
68
|
+
value = doc.get(field)
|
|
69
|
+
if value is not None and (not isinstance(value, str) or not value.strip() or value != value.strip()):
|
|
70
|
+
raise ValidationError(f"{field} must be a nonblank string without surrounding whitespace")
|
|
71
|
+
if doc.get("external_reference") and not doc.get("external_source"):
|
|
72
|
+
raise ValidationError("external_reference requires external_source")
|
|
73
|
+
if doc._persisted:
|
|
74
|
+
stored = get_db().get_value(doc.DOCTYPE, doc.name, ["external_source", "external_reference"])
|
|
75
|
+
for field in ("external_source", "external_reference"):
|
|
76
|
+
if stored and stored.get(field) and stored[field] != doc.get(field):
|
|
77
|
+
raise ValidationError(f"Cannot change {field} after it has been assigned")
|
|
78
|
+
seen = set()
|
|
79
|
+
for item in doc.get("items") or []:
|
|
80
|
+
ref = item.get("external_line_reference")
|
|
81
|
+
if ref is None:
|
|
82
|
+
continue
|
|
83
|
+
if not isinstance(ref, str) or not ref.strip() or ref != ref.strip():
|
|
84
|
+
raise ValidationError("external_line_reference must be a nonblank string without surrounding whitespace")
|
|
85
|
+
if ref in seen:
|
|
86
|
+
raise ValidationError("Duplicate external_line_reference in this document")
|
|
87
|
+
seen.add(ref)
|
|
88
|
+
if not doc._data.get("external_source"):
|
|
89
|
+
return
|
|
90
|
+
if doc._data.get("ignore_pricing_rule") is None:
|
|
91
|
+
doc._data["ignore_pricing_rule"] = 1
|
|
92
|
+
if doc.DOCTYPE in {"Sales Invoice", "Purchase Invoice", "POS Invoice"} and doc.get("update_stock") is None:
|
|
93
|
+
# POS's database default is 1. Persist the bill-only policy explicitly
|
|
94
|
+
# so loading or returning this document cannot enable stock movement.
|
|
95
|
+
doc._data["update_stock"] = 0
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def derived_pricing_fields(source, *, is_return=False):
|
|
99
|
+
"""Carry price/stock policy, but never reuse an imported document identity.
|
|
100
|
+
|
|
101
|
+
Partial invoices and credits are new documents. Their connector must assign
|
|
102
|
+
a new external_reference; external_source alone retains the stock guard.
|
|
103
|
+
Returns reverse historical prices, regardless of today's pricing rules.
|
|
104
|
+
"""
|
|
105
|
+
return {
|
|
106
|
+
"ignore_pricing_rule": 1 if is_return else source.get("ignore_pricing_rule"),
|
|
107
|
+
"external_source": source.get("external_source"),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def derived_line_pricing_fields(item):
|
|
112
|
+
return {field: item.get(field) for field in (
|
|
113
|
+
"ignore_pricing_rule", "external_line_reference", "pricing_rule",
|
|
114
|
+
)}
|