mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-04 12:49:10 +00:00
Merge branch 'develop' into handle-party-type-erpenxt-crm-integration
This commit is contained in:
4
.github/release.yml
vendored
Normal file
4
.github/release.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
changelog:
|
||||||
|
exclude:
|
||||||
|
labels:
|
||||||
|
- skip-release-notes
|
||||||
1
.github/workflows/lock.yml
vendored
1
.github/workflows/lock.yml
vendored
@@ -19,4 +19,3 @@ jobs:
|
|||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-inactive-days: 14
|
issue-inactive-days: 14
|
||||||
pr-inactive-days: 14
|
pr-inactive-days: 14
|
||||||
exclude-pr-created-before: 2025-01-01
|
|
||||||
|
|||||||
@@ -114,10 +114,7 @@ To setup the repository locally follow the steps mentioned below:
|
|||||||
2. In a separate terminal window, run the following commands:
|
2. In a separate terminal window, run the following commands:
|
||||||
```
|
```
|
||||||
# Create a new site
|
# Create a new site
|
||||||
bench new-site erpnext.dev
|
bench new-site erpnext.localhost
|
||||||
|
|
||||||
# Map your site to localhost
|
|
||||||
bench --site erpnext.dev add-to-hosts
|
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Get the ERPNext app and install it
|
3. Get the ERPNext app and install it
|
||||||
@@ -126,10 +123,10 @@ To setup the repository locally follow the steps mentioned below:
|
|||||||
bench get-app https://github.com/frappe/erpnext
|
bench get-app https://github.com/frappe/erpnext
|
||||||
|
|
||||||
# Install the app
|
# Install the app
|
||||||
bench --site erpnext.dev install-app erpnext
|
bench --site erpnext.localhost install-app erpnext
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Open the URL `http://erpnext.dev:8000/app` in your browser, you should see the app running
|
4. Open the URL `http://erpnext.localhost:8000/app` in your browser, you should see the app running
|
||||||
|
|
||||||
## Learning and community
|
## Learning and community
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
files:
|
files:
|
||||||
- source: /erpnext/locale/main.pot
|
- source: /erpnext/locale/main.pot
|
||||||
translation: /erpnext/locale/%two_letters_code%.po
|
translation: /erpnext/locale/%locale_with_underscore%.po
|
||||||
pull_request_title: "fix: sync translations from crowdin"
|
pull_request_title: "fix: sync translations from crowdin"
|
||||||
pull_request_labels:
|
pull_request_labels:
|
||||||
- translation
|
- translation
|
||||||
|
|||||||
@@ -500,7 +500,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
|
|||||||
"name",
|
"name",
|
||||||
)
|
)
|
||||||
|
|
||||||
if old_name:
|
if old_name and not from_descendant:
|
||||||
# same account in parent company exists
|
# same account in parent company exists
|
||||||
allow_child_account_creation = _("Allow Account Creation Against Child Company")
|
allow_child_account_creation = _("Allow Account Creation Against Child Company")
|
||||||
|
|
||||||
@@ -605,6 +605,7 @@ def _ensure_idle_system():
|
|||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
last_gl_update = None
|
||||||
try:
|
try:
|
||||||
# We also lock inserts to GL entry table with for_update here.
|
# We also lock inserts to GL entry table with for_update here.
|
||||||
last_gl_update = frappe.db.get_value("GL Entry", {}, "modified", for_update=True, wait=False)
|
last_gl_update = frappe.db.get_value("GL Entry", {}, "modified", for_update=True, wait=False)
|
||||||
@@ -612,6 +613,9 @@ def _ensure_idle_system():
|
|||||||
# wait=False fails immediately if there's an active transaction.
|
# wait=False fails immediately if there's an active transaction.
|
||||||
last_gl_update = add_to_date(None, seconds=-1)
|
last_gl_update = add_to_date(None, seconds=-1)
|
||||||
|
|
||||||
|
if not last_gl_update:
|
||||||
|
return
|
||||||
|
|
||||||
if last_gl_update > add_to_date(None, minutes=-5):
|
if last_gl_update > add_to_date(None, minutes=-5):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
"Office Maintenance Expenses": {},
|
"Office Maintenance Expenses": {},
|
||||||
"Office Rent": {},
|
"Office Rent": {},
|
||||||
"Postal Expenses": {},
|
"Postal Expenses": {},
|
||||||
"Print and Stationary": {},
|
"Print and Stationery": {},
|
||||||
"Rounded Off": {
|
"Rounded Off": {
|
||||||
"account_type": "Round Off"
|
"account_type": "Round Off"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -374,9 +374,36 @@ def auto_reconcile_vouchers(
|
|||||||
to_reference_date=None,
|
to_reference_date=None,
|
||||||
):
|
):
|
||||||
frappe.flags.auto_reconcile_vouchers = True
|
frappe.flags.auto_reconcile_vouchers = True
|
||||||
reconciled, partially_reconciled = set(), set()
|
|
||||||
|
|
||||||
bank_transactions = get_bank_transactions(bank_account)
|
bank_transactions = get_bank_transactions(bank_account)
|
||||||
|
|
||||||
|
if len(bank_transactions) > 10:
|
||||||
|
frappe.enqueue(
|
||||||
|
method="erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.start_auto_reconcile",
|
||||||
|
queue="long",
|
||||||
|
bank_transactions=bank_transactions,
|
||||||
|
from_date=from_date,
|
||||||
|
to_date=to_date,
|
||||||
|
filter_by_reference_date=filter_by_reference_date,
|
||||||
|
from_reference_date=from_reference_date,
|
||||||
|
to_reference_date=to_reference_date,
|
||||||
|
)
|
||||||
|
frappe.msgprint(_("Auto Reconciliation has started in the background"))
|
||||||
|
else:
|
||||||
|
start_auto_reconcile(
|
||||||
|
bank_transactions,
|
||||||
|
from_date,
|
||||||
|
to_date,
|
||||||
|
filter_by_reference_date,
|
||||||
|
from_reference_date,
|
||||||
|
to_reference_date,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def start_auto_reconcile(
|
||||||
|
bank_transactions, from_date, to_date, filter_by_reference_date, from_reference_date, to_reference_date
|
||||||
|
):
|
||||||
|
reconciled, partially_reconciled = set(), set()
|
||||||
for transaction in bank_transactions:
|
for transaction in bank_transactions:
|
||||||
linked_payments = get_linked_payments(
|
linked_payments = get_linked_payments(
|
||||||
transaction.name,
|
transaction.name,
|
||||||
@@ -414,7 +441,6 @@ def auto_reconcile_vouchers(
|
|||||||
frappe.msgprint(title=_("Auto Reconciliation"), msg=alert_message, indicator=indicator)
|
frappe.msgprint(title=_("Auto Reconciliation"), msg=alert_message, indicator=indicator)
|
||||||
|
|
||||||
frappe.flags.auto_reconcile_vouchers = False
|
frappe.flags.auto_reconcile_vouchers = False
|
||||||
return reconciled, partially_reconciled
|
|
||||||
|
|
||||||
|
|
||||||
def get_auto_reconcile_message(partially_reconciled, reconciled):
|
def get_auto_reconcile_message(partially_reconciled, reconciled):
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class TestCouponCode(IntegrationTestCase):
|
|||||||
item_code="_Test Tesla Car",
|
item_code="_Test Tesla Car",
|
||||||
rate=5000,
|
rate=5000,
|
||||||
qty=1,
|
qty=1,
|
||||||
do_not_submit=True,
|
do_not_save=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(so.items[0].rate, 5000)
|
self.assertEqual(so.items[0].rate, 5000)
|
||||||
|
|||||||
@@ -279,7 +279,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "transaction_exchange_rate",
|
"fieldname": "transaction_exchange_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Transaction Exchange Rate"
|
"label": "Transaction Exchange Rate",
|
||||||
|
"precision": "9"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "debit_in_transaction_currency",
|
"fieldname": "debit_in_transaction_currency",
|
||||||
@@ -357,7 +358,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-08-22 13:03:39.997475",
|
"modified": "2025-02-21 14:36:49.431166",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "GL Entry",
|
"name": "GL Entry",
|
||||||
|
|||||||
@@ -1063,14 +1063,15 @@ class JournalEntry(AccountsController):
|
|||||||
gl_map = []
|
gl_map = []
|
||||||
|
|
||||||
company_currency = erpnext.get_company_currency(self.company)
|
company_currency = erpnext.get_company_currency(self.company)
|
||||||
|
self.transaction_currency = company_currency
|
||||||
|
self.transaction_exchange_rate = 1
|
||||||
if self.multi_currency:
|
if self.multi_currency:
|
||||||
for row in self.get("accounts"):
|
for row in self.get("accounts"):
|
||||||
if row.account_currency != company_currency:
|
if row.account_currency != company_currency:
|
||||||
self.currency = row.account_currency
|
# Journal assumes the first foreign currency as transaction currency
|
||||||
self.conversion_rate = row.exchange_rate
|
self.transaction_currency = row.account_currency
|
||||||
|
self.transaction_exchange_rate = row.exchange_rate
|
||||||
break
|
break
|
||||||
else:
|
|
||||||
self.currency = company_currency
|
|
||||||
|
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
|
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
|
||||||
@@ -1095,6 +1096,18 @@ class JournalEntry(AccountsController):
|
|||||||
"credit_in_account_currency": flt(
|
"credit_in_account_currency": flt(
|
||||||
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
||||||
),
|
),
|
||||||
|
"transaction_currency": self.transaction_currency,
|
||||||
|
"transaction_exchange_rate": self.transaction_exchange_rate,
|
||||||
|
"debit_in_transaction_currency": flt(
|
||||||
|
d.debit_in_account_currency, d.precision("debit_in_account_currency")
|
||||||
|
)
|
||||||
|
if self.transaction_currency == d.account_currency
|
||||||
|
else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate,
|
||||||
|
"credit_in_transaction_currency": flt(
|
||||||
|
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
||||||
|
)
|
||||||
|
if self.transaction_currency == d.account_currency
|
||||||
|
else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate,
|
||||||
"against_voucher_type": d.reference_type,
|
"against_voucher_type": d.reference_type,
|
||||||
"against_voucher": d.reference_name,
|
"against_voucher": d.reference_name,
|
||||||
"remarks": remarks,
|
"remarks": remarks,
|
||||||
|
|||||||
@@ -583,7 +583,7 @@ class TestJournalEntry(IntegrationTestCase):
|
|||||||
order_by="account",
|
order_by="account",
|
||||||
)
|
)
|
||||||
expected = [
|
expected = [
|
||||||
{"account": "_Test Bank - _TC", "transaction_exchange_rate": 1.0},
|
{"account": "_Test Bank - _TC", "transaction_exchange_rate": 85.0},
|
||||||
{"account": "_Test Receivable USD - _TC", "transaction_exchange_rate": 85.0},
|
{"account": "_Test Receivable USD - _TC", "transaction_exchange_rate": 85.0},
|
||||||
]
|
]
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|||||||
@@ -1311,15 +1311,22 @@ class PaymentEntry(AccountsController):
|
|||||||
|
|
||||||
self.set("remarks", "\n".join(remarks))
|
self.set("remarks", "\n".join(remarks))
|
||||||
|
|
||||||
|
def set_transaction_currency_and_rate(self):
|
||||||
|
company_currency = erpnext.get_company_currency(self.company)
|
||||||
|
self.transaction_currency = company_currency
|
||||||
|
self.transaction_exchange_rate = 1
|
||||||
|
|
||||||
|
if self.paid_from_account_currency != company_currency:
|
||||||
|
self.transaction_currency = self.paid_from_account_currency
|
||||||
|
self.transaction_exchange_rate = self.source_exchange_rate
|
||||||
|
elif self.paid_to_account_currency != company_currency:
|
||||||
|
self.transaction_currency = self.paid_to_account_currency
|
||||||
|
self.transaction_exchange_rate = self.target_exchange_rate
|
||||||
|
|
||||||
def build_gl_map(self):
|
def build_gl_map(self):
|
||||||
if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"):
|
if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"):
|
||||||
self.setup_party_account_field()
|
self.setup_party_account_field()
|
||||||
|
self.set_transaction_currency_and_rate()
|
||||||
company_currency = erpnext.get_company_currency(self.company)
|
|
||||||
if self.paid_from_account_currency != company_currency:
|
|
||||||
self.currency = self.paid_from_account_currency
|
|
||||||
elif self.paid_to_account_currency != company_currency:
|
|
||||||
self.currency = self.paid_to_account_currency
|
|
||||||
|
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
self.add_party_gl_entries(gl_entries)
|
self.add_party_gl_entries(gl_entries)
|
||||||
@@ -1400,6 +1407,9 @@ class PaymentEntry(AccountsController):
|
|||||||
"cost_center": cost_center,
|
"cost_center": cost_center,
|
||||||
dr_or_cr + "_in_account_currency": d.allocated_amount,
|
dr_or_cr + "_in_account_currency": d.allocated_amount,
|
||||||
dr_or_cr: allocated_amount_in_company_currency,
|
dr_or_cr: allocated_amount_in_company_currency,
|
||||||
|
dr_or_cr + "_in_transaction_currency": d.allocated_amount
|
||||||
|
if self.transaction_currency == self.party_account_currency
|
||||||
|
else allocated_amount_in_company_currency / self.transaction_exchange_rate,
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
)
|
)
|
||||||
@@ -1444,6 +1454,9 @@ class PaymentEntry(AccountsController):
|
|||||||
"account_currency": self.party_account_currency,
|
"account_currency": self.party_account_currency,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
dr_or_cr + "_in_account_currency": self.unallocated_amount,
|
dr_or_cr + "_in_account_currency": self.unallocated_amount,
|
||||||
|
dr_or_cr + "_in_transaction_currency": self.unallocated_amount
|
||||||
|
if self.party_account_currency == self.transaction_currency
|
||||||
|
else base_unallocated_amount / self.transaction_exchange_rate,
|
||||||
dr_or_cr: base_unallocated_amount,
|
dr_or_cr: base_unallocated_amount,
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
@@ -1461,6 +1474,7 @@ class PaymentEntry(AccountsController):
|
|||||||
def make_advance_gl_entries(
|
def make_advance_gl_entries(
|
||||||
self, entry: object | dict = None, cancel: bool = 0, update_outstanding: str = "Yes"
|
self, entry: object | dict = None, cancel: bool = 0, update_outstanding: str = "Yes"
|
||||||
):
|
):
|
||||||
|
self.set_transaction_currency_and_rate()
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
self.add_advance_gl_entries(gl_entries, entry)
|
self.add_advance_gl_entries(gl_entries, entry)
|
||||||
|
|
||||||
@@ -1540,9 +1554,16 @@ class PaymentEntry(AccountsController):
|
|||||||
frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date)
|
frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date)
|
||||||
|
|
||||||
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
|
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
|
||||||
|
base_allocated_amount = self.calculate_base_allocated_amount_for_reference(invoice)
|
||||||
args_dict["account"] = account
|
args_dict["account"] = account
|
||||||
args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice)
|
args_dict[dr_or_cr] = base_allocated_amount
|
||||||
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
|
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
|
||||||
|
args_dict[dr_or_cr + "_in_transaction_currency"] = (
|
||||||
|
invoice.allocated_amount
|
||||||
|
if self.party_account_currency == self.transaction_currency
|
||||||
|
else base_allocated_amount / self.transaction_exchange_rate
|
||||||
|
)
|
||||||
|
|
||||||
args_dict.update(
|
args_dict.update(
|
||||||
{
|
{
|
||||||
"against_voucher_type": invoice.reference_doctype,
|
"against_voucher_type": invoice.reference_doctype,
|
||||||
@@ -1560,8 +1581,13 @@ class PaymentEntry(AccountsController):
|
|||||||
args_dict[dr_or_cr + "_in_account_currency"] = 0
|
args_dict[dr_or_cr + "_in_account_currency"] = 0
|
||||||
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
|
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
|
||||||
args_dict["account"] = self.party_account
|
args_dict["account"] = self.party_account
|
||||||
args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice)
|
args_dict[dr_or_cr] = base_allocated_amount
|
||||||
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
|
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
|
||||||
|
args_dict[dr_or_cr + "_in_transaction_currency"] = (
|
||||||
|
invoice.allocated_amount
|
||||||
|
if self.party_account_currency == self.transaction_currency
|
||||||
|
else base_allocated_amount / self.transaction_exchange_rate
|
||||||
|
)
|
||||||
args_dict.update(
|
args_dict.update(
|
||||||
{
|
{
|
||||||
"against_voucher_type": "Payment Entry",
|
"against_voucher_type": "Payment Entry",
|
||||||
@@ -1583,6 +1609,9 @@ class PaymentEntry(AccountsController):
|
|||||||
"account_currency": self.paid_from_account_currency,
|
"account_currency": self.paid_from_account_currency,
|
||||||
"against": self.party if self.payment_type == "Pay" else self.paid_to,
|
"against": self.party if self.payment_type == "Pay" else self.paid_to,
|
||||||
"credit_in_account_currency": self.paid_amount,
|
"credit_in_account_currency": self.paid_amount,
|
||||||
|
"credit_in_transaction_currency": self.paid_amount
|
||||||
|
if self.paid_from_account_currency == self.transaction_currency
|
||||||
|
else self.base_paid_amount / self.transaction_exchange_rate,
|
||||||
"credit": self.base_paid_amount,
|
"credit": self.base_paid_amount,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"post_net_value": True,
|
"post_net_value": True,
|
||||||
@@ -1598,6 +1627,9 @@ class PaymentEntry(AccountsController):
|
|||||||
"account_currency": self.paid_to_account_currency,
|
"account_currency": self.paid_to_account_currency,
|
||||||
"against": self.party if self.payment_type == "Receive" else self.paid_from,
|
"against": self.party if self.payment_type == "Receive" else self.paid_from,
|
||||||
"debit_in_account_currency": self.received_amount,
|
"debit_in_account_currency": self.received_amount,
|
||||||
|
"debit_in_transaction_currency": self.received_amount
|
||||||
|
if self.paid_to_account_currency == self.transaction_currency
|
||||||
|
else self.base_received_amount / self.transaction_exchange_rate,
|
||||||
"debit": self.base_received_amount,
|
"debit": self.base_received_amount,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
},
|
},
|
||||||
@@ -1633,6 +1665,8 @@ class PaymentEntry(AccountsController):
|
|||||||
dr_or_cr + "_in_account_currency": base_tax_amount
|
dr_or_cr + "_in_account_currency": base_tax_amount
|
||||||
if account_currency == self.company_currency
|
if account_currency == self.company_currency
|
||||||
else d.tax_amount,
|
else d.tax_amount,
|
||||||
|
dr_or_cr + "_in_transaction_currency": base_tax_amount
|
||||||
|
/ self.transaction_exchange_rate,
|
||||||
"cost_center": d.cost_center,
|
"cost_center": d.cost_center,
|
||||||
"post_net_value": True,
|
"post_net_value": True,
|
||||||
},
|
},
|
||||||
@@ -1658,6 +1692,8 @@ class PaymentEntry(AccountsController):
|
|||||||
rev_dr_or_cr + "_in_account_currency": base_tax_amount
|
rev_dr_or_cr + "_in_account_currency": base_tax_amount
|
||||||
if account_currency == self.company_currency
|
if account_currency == self.company_currency
|
||||||
else d.tax_amount,
|
else d.tax_amount,
|
||||||
|
rev_dr_or_cr + "_in_transaction_currency": base_tax_amount
|
||||||
|
/ self.transaction_exchange_rate,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"post_net_value": True,
|
"post_net_value": True,
|
||||||
},
|
},
|
||||||
@@ -1680,6 +1716,7 @@ class PaymentEntry(AccountsController):
|
|||||||
"account_currency": account_currency,
|
"account_currency": account_currency,
|
||||||
"against": self.party or self.paid_from,
|
"against": self.party or self.paid_from,
|
||||||
"debit_in_account_currency": d.amount,
|
"debit_in_account_currency": d.amount,
|
||||||
|
"debit_in_transaction_currency": d.amount / self.transaction_exchange_rate,
|
||||||
"debit": d.amount,
|
"debit": d.amount,
|
||||||
"cost_center": d.cost_center,
|
"cost_center": d.cost_center,
|
||||||
},
|
},
|
||||||
@@ -2950,7 +2987,9 @@ def get_payment_entry(
|
|||||||
pe.paid_amount = paid_amount
|
pe.paid_amount = paid_amount
|
||||||
pe.received_amount = received_amount
|
pe.received_amount = received_amount
|
||||||
pe.letter_head = doc.get("letter_head")
|
pe.letter_head = doc.get("letter_head")
|
||||||
pe.bank_account = frappe.db.get_value("Bank Account", {"is_company_account": 1, "is_default": 1}, "name")
|
pe.bank_account = frappe.db.get_value(
|
||||||
|
"Bank Account", {"is_company_account": 1, "is_default": 1, "company": doc.company}, "name"
|
||||||
|
)
|
||||||
|
|
||||||
if dt in ["Purchase Order", "Sales Order", "Sales Invoice", "Purchase Invoice"]:
|
if dt in ["Purchase Order", "Sales Order", "Sales Invoice", "Purchase Invoice"]:
|
||||||
pe.project = doc.get("project") or reduce(
|
pe.project = doc.get("project") or reduce(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.mapper import map_child_doc, map_doc
|
from frappe.model.mapper import map_child_doc, map_doc
|
||||||
|
from frappe.query_builder import DocType
|
||||||
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
|
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
|
||||||
from frappe.utils.background_jobs import enqueue, is_job_enqueued
|
from frappe.utils.background_jobs import enqueue, is_job_enqueued
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
@@ -119,17 +120,18 @@ class POSInvoiceMergeLog(Document):
|
|||||||
returns = [d for d in pos_invoice_docs if d.get("is_return") == 1]
|
returns = [d for d in pos_invoice_docs if d.get("is_return") == 1]
|
||||||
sales = [d for d in pos_invoice_docs if d.get("is_return") == 0]
|
sales = [d for d in pos_invoice_docs if d.get("is_return") == 0]
|
||||||
|
|
||||||
sales_invoice, credit_note = "", ""
|
sales_invoice, credit_notes = "", {}
|
||||||
sales_invoice_doc = None
|
sales_invoice_doc = None
|
||||||
if sales:
|
if sales:
|
||||||
sales_invoice_doc = self.process_merging_into_sales_invoice(sales)
|
sales_invoice_doc = self.process_merging_into_sales_invoice(sales)
|
||||||
sales_invoice = sales_invoice_doc.name
|
sales_invoice = sales_invoice_doc.name
|
||||||
|
|
||||||
if returns:
|
if returns:
|
||||||
credit_note = self.process_merging_into_credit_note(returns, sales_invoice_doc)
|
distinguished_returns = self.distinguish_return_pos_invoices(returns, sales_invoice_doc)
|
||||||
|
credit_notes = self.process_merging_into_credit_notes(distinguished_returns)
|
||||||
|
|
||||||
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
||||||
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
|
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_notes)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||||
@@ -159,34 +161,50 @@ class POSInvoiceMergeLog(Document):
|
|||||||
|
|
||||||
return sales_invoice
|
return sales_invoice
|
||||||
|
|
||||||
def process_merging_into_credit_note(self, data, sales_invoice_doc=None):
|
def process_merging_into_credit_notes(self, data):
|
||||||
credit_note = self.get_new_sales_invoice()
|
credit_notes = {}
|
||||||
credit_note.is_return = 1
|
for key, value in data.items():
|
||||||
|
if not value:
|
||||||
|
continue
|
||||||
|
|
||||||
credit_note = self.merge_pos_invoice_into(credit_note, data)
|
credit_note = self.get_new_sales_invoice()
|
||||||
referenes = {}
|
credit_note.is_return = 1
|
||||||
|
|
||||||
if sales_invoice_doc:
|
credit_note = self.merge_pos_invoice_into(credit_note, value)
|
||||||
credit_note.return_against = sales_invoice_doc.name
|
credit_note.return_against = key
|
||||||
|
|
||||||
for d in sales_invoice_doc.items:
|
credit_note.is_consolidated = 1
|
||||||
referenes[d.item_code] = d.name
|
credit_note.set_posting_time = 1
|
||||||
|
credit_note.posting_date = getdate(self.posting_date)
|
||||||
|
credit_note.posting_time = get_time(self.posting_time)
|
||||||
|
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
||||||
|
# credit_note.return_against = self.consolidated_invoice
|
||||||
|
credit_note.save()
|
||||||
|
credit_note.submit()
|
||||||
|
|
||||||
for d in credit_note.items:
|
self.consolidated_credit_note = credit_note.name
|
||||||
d.sales_invoice_item = referenes.get(d.item_code)
|
credit_notes[credit_note.name] = [d.name for d in value]
|
||||||
|
|
||||||
credit_note.is_consolidated = 1
|
return credit_notes
|
||||||
credit_note.set_posting_time = 1
|
|
||||||
credit_note.posting_date = getdate(self.posting_date)
|
|
||||||
credit_note.posting_time = get_time(self.posting_time)
|
|
||||||
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
|
||||||
# credit_note.return_against = self.consolidated_invoice
|
|
||||||
credit_note.save()
|
|
||||||
credit_note.submit()
|
|
||||||
|
|
||||||
self.consolidated_credit_note = credit_note.name
|
def distinguish_return_pos_invoices(self, data, sales_invoice_doc=None):
|
||||||
|
return_invoices = {}
|
||||||
|
|
||||||
return credit_note.name
|
return_invoices[sales_invoice_doc.name if sales_invoice_doc else None] = []
|
||||||
|
|
||||||
|
for doc in data:
|
||||||
|
sales_invoices_of_return_against = frappe.db.get_value(
|
||||||
|
"POS Invoice", doc.return_against, "consolidated_invoice"
|
||||||
|
)
|
||||||
|
if sales_invoices_of_return_against:
|
||||||
|
if sales_invoices_of_return_against in return_invoices:
|
||||||
|
return_invoices[sales_invoices_of_return_against].append(doc)
|
||||||
|
else:
|
||||||
|
return_invoices[sales_invoices_of_return_against] = [doc]
|
||||||
|
else:
|
||||||
|
return_invoices[sales_invoice_doc.name if sales_invoice_doc else None].append(doc)
|
||||||
|
|
||||||
|
return return_invoices
|
||||||
|
|
||||||
def merge_pos_invoice_into(self, invoice, data):
|
def merge_pos_invoice_into(self, invoice, data):
|
||||||
items, payments, taxes = [], [], []
|
items, payments, taxes = [], [], []
|
||||||
@@ -212,33 +230,20 @@ class POSInvoiceMergeLog(Document):
|
|||||||
loyalty_amount_sum += doc.loyalty_amount
|
loyalty_amount_sum += doc.loyalty_amount
|
||||||
|
|
||||||
for item in doc.get("items"):
|
for item in doc.get("items"):
|
||||||
found = False
|
item.rate = item.net_rate
|
||||||
for i in items:
|
item.amount = item.net_amount
|
||||||
if (
|
item.base_amount = item.base_net_amount
|
||||||
i.item_code == item.item_code
|
item.price_list_rate = 0
|
||||||
and not i.serial_and_batch_bundle
|
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
|
||||||
and not i.serial_no
|
si_item.pos_invoice = doc.name
|
||||||
and not i.batch_no
|
si_item.pos_invoice_item = item.name
|
||||||
and i.uom == item.uom
|
if doc.is_return:
|
||||||
and i.net_rate == item.net_rate
|
si_item.sales_invoice_item = get_sales_invoice_item(
|
||||||
and i.warehouse == item.warehouse
|
doc.return_against, item.pos_invoice_item
|
||||||
):
|
)
|
||||||
found = True
|
if item.serial_and_batch_bundle:
|
||||||
i.qty = i.qty + item.qty
|
si_item.serial_and_batch_bundle = item.serial_and_batch_bundle
|
||||||
i.amount = i.amount + item.net_amount
|
items.append(si_item)
|
||||||
i.net_amount = i.amount
|
|
||||||
i.base_amount = i.base_amount + item.base_net_amount
|
|
||||||
i.base_net_amount = i.base_amount
|
|
||||||
|
|
||||||
if not found:
|
|
||||||
item.rate = item.net_rate
|
|
||||||
item.amount = item.net_amount
|
|
||||||
item.base_amount = item.base_net_amount
|
|
||||||
item.price_list_rate = 0
|
|
||||||
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
|
|
||||||
if item.serial_and_batch_bundle:
|
|
||||||
si_item.serial_and_batch_bundle = item.serial_and_batch_bundle
|
|
||||||
items.append(si_item)
|
|
||||||
|
|
||||||
for tax in doc.get("taxes"):
|
for tax in doc.get("taxes"):
|
||||||
found = False
|
found = False
|
||||||
@@ -328,16 +333,16 @@ class POSInvoiceMergeLog(Document):
|
|||||||
|
|
||||||
return sales_invoice
|
return sales_invoice
|
||||||
|
|
||||||
def update_pos_invoices(self, invoice_docs, sales_invoice="", credit_note=""):
|
def update_pos_invoices(self, invoice_docs, sales_invoice="", credit_notes=None):
|
||||||
for doc in invoice_docs:
|
for doc in invoice_docs:
|
||||||
doc.load_from_db()
|
doc.load_from_db()
|
||||||
doc.update(
|
inv = sales_invoice
|
||||||
{
|
if doc.is_return:
|
||||||
"consolidated_invoice": None
|
for key, value in credit_notes.items():
|
||||||
if self.docstatus == 2
|
if doc.name in value:
|
||||||
else (credit_note if doc.is_return else sales_invoice)
|
inv = key
|
||||||
}
|
break
|
||||||
)
|
doc.update({"consolidated_invoice": None if self.docstatus == 2 else inv})
|
||||||
doc.set_status(update=True)
|
doc.set_status(update=True)
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
@@ -628,3 +633,26 @@ def get_error_message(message) -> str:
|
|||||||
return message["message"]
|
return message["message"]
|
||||||
except Exception:
|
except Exception:
|
||||||
return str(message)
|
return str(message)
|
||||||
|
|
||||||
|
|
||||||
|
def get_sales_invoice_item(return_against_pos_invoice, pos_invoice_item):
|
||||||
|
try:
|
||||||
|
SalesInvoice = DocType("Sales Invoice")
|
||||||
|
SalesInvoiceItem = DocType("Sales Invoice Item")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(SalesInvoice)
|
||||||
|
.from_(SalesInvoiceItem)
|
||||||
|
.select(SalesInvoiceItem.name)
|
||||||
|
.where(
|
||||||
|
(SalesInvoice.name == SalesInvoiceItem.parent)
|
||||||
|
& (SalesInvoice.is_return == 0)
|
||||||
|
& (SalesInvoiceItem.pos_invoice == return_against_pos_invoice)
|
||||||
|
& (SalesInvoiceItem.pos_invoice_item == pos_invoice_item)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result = query.run(as_dict=True)
|
||||||
|
return result[0].name if result else None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|||||||
@@ -454,8 +454,7 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
|
|||||||
|
|
||||||
if pricing_rule.coupon_code_based == 1:
|
if pricing_rule.coupon_code_based == 1:
|
||||||
if not args.coupon_code:
|
if not args.coupon_code:
|
||||||
return item_details
|
continue
|
||||||
|
|
||||||
coupon_code = frappe.db.get_value(
|
coupon_code = frappe.db.get_value(
|
||||||
doctype="Coupon Code", filters={"pricing_rule": pricing_rule.name}, fieldname="name"
|
doctype="Coupon Code", filters={"pricing_rule": pricing_rule.name}, fieldname="name"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -68,6 +68,14 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
|
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
|
||||||
this.frm.trigger("supplier");
|
this.frm.trigger("supplier");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.frm.set_query("supplier", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_transporter: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh(doc) {
|
refresh(doc) {
|
||||||
|
|||||||
@@ -873,6 +873,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.make_payment_gl_entries(gl_entries)
|
self.make_payment_gl_entries(gl_entries)
|
||||||
self.make_write_off_gl_entry(gl_entries)
|
self.make_write_off_gl_entry(gl_entries)
|
||||||
self.make_gle_for_rounding_adjustment(gl_entries)
|
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||||
|
self.set_transaction_currency_and_rate_in_gl_map(gl_entries)
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
def check_asset_cwip_enabled(self):
|
def check_asset_cwip_enabled(self):
|
||||||
@@ -918,6 +919,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"credit_in_account_currency": base_grand_total
|
"credit_in_account_currency": base_grand_total
|
||||||
if self.party_account_currency == self.company_currency
|
if self.party_account_currency == self.company_currency
|
||||||
else grand_total,
|
else grand_total,
|
||||||
|
"credit_in_transaction_currency": grand_total,
|
||||||
"against_voucher": against_voucher,
|
"against_voucher": against_voucher,
|
||||||
"against_voucher_type": self.doctype,
|
"against_voucher_type": self.doctype,
|
||||||
"project": self.project,
|
"project": self.project,
|
||||||
@@ -953,7 +955,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
valuation_tax_accounts = [
|
valuation_tax_accounts = [
|
||||||
d.account_head
|
d.account_head
|
||||||
for d in self.get("taxes")
|
for d in self.get("taxes")
|
||||||
if d.category in ("Valuation", "Total and Valuation")
|
if d.category in ("Valuation", "Valuation and Total")
|
||||||
and flt(d.base_tax_amount_after_discount_amount)
|
and flt(d.base_tax_amount_after_discount_amount)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -969,7 +971,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if flt(item.base_net_amount):
|
if flt(item.base_net_amount):
|
||||||
account_currency = get_account_currency(item.expense_account)
|
|
||||||
if item.item_code:
|
if item.item_code:
|
||||||
frappe.get_cached_value("Item", item.item_code, "asset_category")
|
frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||||
|
|
||||||
@@ -978,6 +979,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
and self.auto_accounting_for_stock
|
and self.auto_accounting_for_stock
|
||||||
and (item.item_code in stock_items or item.is_fixed_asset)
|
and (item.item_code in stock_items or item.is_fixed_asset)
|
||||||
):
|
):
|
||||||
|
account_currency = get_account_currency(item.expense_account)
|
||||||
# warehouse account
|
# warehouse account
|
||||||
warehouse_debit_amount = self.make_stock_adjustment_entry(
|
warehouse_debit_amount = self.make_stock_adjustment_entry(
|
||||||
gl_entries, item, voucher_wise_stock_value, account_currency
|
gl_entries, item, voucher_wise_stock_value, account_currency
|
||||||
@@ -993,6 +995,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"debit": warehouse_debit_amount,
|
"debit": warehouse_debit_amount,
|
||||||
|
"debit_in_transaction_currency": item.net_amount,
|
||||||
},
|
},
|
||||||
warehouse_account[item.warehouse]["account_currency"],
|
warehouse_account[item.warehouse]["account_currency"],
|
||||||
item=item,
|
item=item,
|
||||||
@@ -1013,6 +1016,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
|
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
|
||||||
|
"debit_in_transaction_currency": item.net_amount,
|
||||||
},
|
},
|
||||||
warehouse_account[item.from_warehouse]["account_currency"],
|
warehouse_account[item.from_warehouse]["account_currency"],
|
||||||
item=item,
|
item=item,
|
||||||
@@ -1027,6 +1031,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"account": item.expense_account,
|
"account": item.expense_account,
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
|
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
|
||||||
|
"debit_in_transaction_currency": item.net_amount,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"project": item.project,
|
"project": item.project,
|
||||||
@@ -1044,6 +1049,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"account": item.expense_account,
|
"account": item.expense_account,
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"debit": warehouse_debit_amount,
|
"debit": warehouse_debit_amount,
|
||||||
|
"debit_in_transaction_currency": flt(
|
||||||
|
warehouse_debit_amount / self.conversion_rate,
|
||||||
|
item.precision("net_amount"),
|
||||||
|
),
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
@@ -1056,7 +1065,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# Amount added through landed-cost-voucher
|
# Amount added through landed-cost-voucher
|
||||||
if landed_cost_entries:
|
if landed_cost_entries:
|
||||||
if (item.item_code, item.name) in landed_cost_entries:
|
if (item.item_code, item.name) in landed_cost_entries:
|
||||||
for account, amount in landed_cost_entries[(item.item_code, item.name)].items():
|
for account, base_amount in landed_cost_entries[
|
||||||
|
(item.item_code, item.name)
|
||||||
|
].items():
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
@@ -1064,8 +1075,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"against": item.expense_account,
|
"against": item.expense_account,
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"credit": flt(amount["base_amount"]),
|
"credit": flt(base_amount["base_amount"]),
|
||||||
"credit_in_account_currency": flt(amount["amount"]),
|
"credit_in_account_currency": flt(base_amount["amount"]),
|
||||||
|
"credit_in_transaction_currency": item.net_amount,
|
||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
},
|
},
|
||||||
item=item,
|
item=item,
|
||||||
@@ -1088,6 +1100,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"credit": flt(item.rm_supp_cost),
|
"credit": flt(item.rm_supp_cost),
|
||||||
|
"credit_in_transaction_currency": item.net_amount,
|
||||||
},
|
},
|
||||||
warehouse_account[self.supplier_warehouse]["account_currency"],
|
warehouse_account[self.supplier_warehouse]["account_currency"],
|
||||||
item=item,
|
item=item,
|
||||||
@@ -1101,7 +1114,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
else item.deferred_expense_account
|
else item.deferred_expense_account
|
||||||
)
|
)
|
||||||
|
|
||||||
dummy, amount = self.get_amount_and_base_amount(item, None)
|
account_currency = get_account_currency(expense_account)
|
||||||
|
amount, base_amount = self.get_amount_and_base_amount(item, None)
|
||||||
|
|
||||||
if provisional_accounting_for_non_stock_items:
|
if provisional_accounting_for_non_stock_items:
|
||||||
self.make_provisional_gl_entry(gl_entries, item)
|
self.make_provisional_gl_entry(gl_entries, item)
|
||||||
@@ -1112,7 +1126,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
{
|
{
|
||||||
"account": expense_account,
|
"account": expense_account,
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"debit": amount,
|
"debit": base_amount,
|
||||||
|
"debit_in_transaction_currency": amount,
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
},
|
},
|
||||||
@@ -1186,6 +1201,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"account": self.stock_received_but_not_billed,
|
"account": self.stock_received_but_not_billed,
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
||||||
|
"debit_in_transaction_currency": flt(
|
||||||
|
item.item_tax_amount / self.conversion_rate,
|
||||||
|
item.precision("item_tax_amount"),
|
||||||
|
),
|
||||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
@@ -1305,6 +1324,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"account": cost_of_goods_sold_account,
|
"account": cost_of_goods_sold_account,
|
||||||
"against": item.expense_account,
|
"against": item.expense_account,
|
||||||
"debit": stock_adjustment_amt,
|
"debit": stock_adjustment_amt,
|
||||||
|
"debit_in_transaction_currency": item.net_amount,
|
||||||
"remarks": self.get("remarks") or _("Stock Adjustment"),
|
"remarks": self.get("remarks") or _("Stock Adjustment"),
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
@@ -1338,6 +1358,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
dr_or_cr + "_in_account_currency": base_amount
|
dr_or_cr + "_in_account_currency": base_amount
|
||||||
if account_currency == self.company_currency
|
if account_currency == self.company_currency
|
||||||
else amount,
|
else amount,
|
||||||
|
dr_or_cr + "_in_transaction_currency": amount,
|
||||||
"cost_center": tax.cost_center,
|
"cost_center": tax.cost_center,
|
||||||
},
|
},
|
||||||
account_currency,
|
account_currency,
|
||||||
@@ -1384,6 +1405,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"cost_center": tax.cost_center,
|
"cost_center": tax.cost_center,
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"credit": applicable_amount,
|
"credit": applicable_amount,
|
||||||
|
"credit_in_transaction_currency": flt(
|
||||||
|
applicable_amount / self.conversion_rate,
|
||||||
|
frappe.get_precision("Purchase Invoice Item", "item_tax_amount"),
|
||||||
|
),
|
||||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||||
},
|
},
|
||||||
item=tax,
|
item=tax,
|
||||||
@@ -1402,6 +1427,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"cost_center": tax.cost_center,
|
"cost_center": tax.cost_center,
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"credit": valuation_tax[tax.name],
|
"credit": valuation_tax[tax.name],
|
||||||
|
"credit_in_transaction_currency": flt(
|
||||||
|
valuation_tax[tax.name] / self.conversion_rate,
|
||||||
|
frappe.get_precision("Purchase Invoice Item", "item_tax_amount"),
|
||||||
|
),
|
||||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||||
},
|
},
|
||||||
item=tax,
|
item=tax,
|
||||||
@@ -1417,6 +1446,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"account": self.unrealized_profit_loss_account,
|
"account": self.unrealized_profit_loss_account,
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"credit": flt(self.total_taxes_and_charges),
|
"credit": flt(self.total_taxes_and_charges),
|
||||||
|
"credit_in_transaction_currency": flt(self.total_taxes_and_charges),
|
||||||
"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
|
"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
},
|
},
|
||||||
@@ -1466,6 +1496,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"debit_in_account_currency": self.base_paid_amount
|
"debit_in_account_currency": self.base_paid_amount
|
||||||
if self.party_account_currency == self.company_currency
|
if self.party_account_currency == self.company_currency
|
||||||
else self.paid_amount,
|
else self.paid_amount,
|
||||||
|
"debit_in_transaction_currency": self.paid_amount,
|
||||||
"against_voucher": self.return_against
|
"against_voucher": self.return_against
|
||||||
if cint(self.is_return) and self.return_against
|
if cint(self.is_return) and self.return_against
|
||||||
else self.name,
|
else self.name,
|
||||||
@@ -1487,6 +1518,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"credit_in_account_currency": self.base_paid_amount
|
"credit_in_account_currency": self.base_paid_amount
|
||||||
if bank_account_currency == self.company_currency
|
if bank_account_currency == self.company_currency
|
||||||
else self.paid_amount,
|
else self.paid_amount,
|
||||||
|
"credit_in_transaction_currency": self.paid_amount,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
},
|
},
|
||||||
bank_account_currency,
|
bank_account_currency,
|
||||||
@@ -1511,6 +1543,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"debit_in_account_currency": self.base_write_off_amount
|
"debit_in_account_currency": self.base_write_off_amount
|
||||||
if self.party_account_currency == self.company_currency
|
if self.party_account_currency == self.company_currency
|
||||||
else self.write_off_amount,
|
else self.write_off_amount,
|
||||||
|
"debit_in_transaction_currency": self.write_off_amount,
|
||||||
"against_voucher": self.return_against
|
"against_voucher": self.return_against
|
||||||
if cint(self.is_return) and self.return_against
|
if cint(self.is_return) and self.return_against
|
||||||
else self.name,
|
else self.name,
|
||||||
@@ -1531,6 +1564,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"credit_in_account_currency": self.base_write_off_amount
|
"credit_in_account_currency": self.base_write_off_amount
|
||||||
if write_off_account_currency == self.company_currency
|
if write_off_account_currency == self.company_currency
|
||||||
else self.write_off_amount,
|
else self.write_off_amount,
|
||||||
|
"credit_in_transaction_currency": self.write_off_amount,
|
||||||
"cost_center": self.cost_center or self.write_off_cost_center,
|
"cost_center": self.cost_center or self.write_off_cost_center,
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
|
|||||||
@@ -2656,6 +2656,50 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
|
|||||||
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", original_value
|
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", original_value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_trx_currency_debit_credit_for_high_precision(self):
|
||||||
|
exc_rate = 0.737517516
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
currency="USD", conversion_rate=exc_rate, qty=1, rate=2000, do_not_save=True
|
||||||
|
)
|
||||||
|
pi.supplier = "_Test Supplier USD"
|
||||||
|
pi.save().submit()
|
||||||
|
|
||||||
|
expected = (
|
||||||
|
("_Test Account Cost for Goods Sold - _TC", 1475.04, 0.0, 2000.0, 0.0, "USD", exc_rate),
|
||||||
|
("_Test Payable USD - _TC", 0.0, 1475.04, 0.0, 2000.0, "USD", exc_rate),
|
||||||
|
)
|
||||||
|
|
||||||
|
actual = frappe.db.get_all(
|
||||||
|
"GL Entry",
|
||||||
|
filters={"voucher_no": pi.name},
|
||||||
|
fields=[
|
||||||
|
"account",
|
||||||
|
"debit",
|
||||||
|
"credit",
|
||||||
|
"debit_in_transaction_currency",
|
||||||
|
"credit_in_transaction_currency",
|
||||||
|
"transaction_currency",
|
||||||
|
"transaction_exchange_rate",
|
||||||
|
],
|
||||||
|
order_by="account",
|
||||||
|
as_list=1,
|
||||||
|
)
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
def test_prevents_fully_returned_invoice_with_zero_quantity(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import StockOverReturnError, make_return_doc
|
||||||
|
|
||||||
|
invoice = make_purchase_invoice(qty=10)
|
||||||
|
|
||||||
|
return_doc = make_return_doc(invoice.doctype, invoice.name)
|
||||||
|
return_doc.items[0].qty = -10
|
||||||
|
return_doc.save().submit()
|
||||||
|
|
||||||
|
return_doc = make_return_doc(invoice.doctype, invoice.name)
|
||||||
|
return_doc.items[0].qty = 0
|
||||||
|
|
||||||
|
self.assertRaises(StockOverReturnError, return_doc.save)
|
||||||
|
|
||||||
|
|
||||||
def set_advance_flag(company, flag, default_account):
|
def set_advance_flag(company, flag, default_account):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
|
|||||||
@@ -462,7 +462,8 @@
|
|||||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||||
"fieldname": "serial_no",
|
"fieldname": "serial_no",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"label": "Serial No"
|
"label": "Serial No",
|
||||||
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||||
@@ -979,16 +980,18 @@
|
|||||||
"options": "currency"
|
"options": "currency"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-10-28 15:06:19.246141",
|
"modified": "2025-03-07 10:21:59.960021",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
"naming_rule": "Random",
|
"naming_rule": "Random",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
|
|||||||
@@ -679,7 +679,13 @@ class SalesInvoice(SellingController):
|
|||||||
"Account", self.debit_to, "account_currency", cache=True
|
"Account", self.debit_to, "account_currency", cache=True
|
||||||
)
|
)
|
||||||
if not self.due_date and self.customer:
|
if not self.due_date and self.customer:
|
||||||
self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
|
self.due_date = get_due_date(
|
||||||
|
self.posting_date,
|
||||||
|
"Customer",
|
||||||
|
self.customer,
|
||||||
|
self.company,
|
||||||
|
template_name=self.payment_terms_template,
|
||||||
|
)
|
||||||
|
|
||||||
super().set_missing_values(for_validate)
|
super().set_missing_values(for_validate)
|
||||||
|
|
||||||
@@ -1238,6 +1244,7 @@ class SalesInvoice(SellingController):
|
|||||||
self.make_write_off_gl_entry(gl_entries)
|
self.make_write_off_gl_entry(gl_entries)
|
||||||
self.make_gle_for_rounding_adjustment(gl_entries)
|
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||||
|
|
||||||
|
self.set_transaction_currency_and_rate_in_gl_map(gl_entries)
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
def make_customer_gl_entry(self, gl_entries):
|
def make_customer_gl_entry(self, gl_entries):
|
||||||
@@ -1271,6 +1278,7 @@ class SalesInvoice(SellingController):
|
|||||||
"debit_in_account_currency": base_grand_total
|
"debit_in_account_currency": base_grand_total
|
||||||
if self.party_account_currency == self.company_currency
|
if self.party_account_currency == self.company_currency
|
||||||
else grand_total,
|
else grand_total,
|
||||||
|
"debit_in_transaction_currency": grand_total,
|
||||||
"against_voucher": against_voucher,
|
"against_voucher": against_voucher,
|
||||||
"against_voucher_type": self.doctype,
|
"against_voucher_type": self.doctype,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
@@ -1302,6 +1310,9 @@ class SalesInvoice(SellingController):
|
|||||||
if account_currency == self.company_currency
|
if account_currency == self.company_currency
|
||||||
else flt(amount, tax.precision("tax_amount_after_discount_amount"))
|
else flt(amount, tax.precision("tax_amount_after_discount_amount"))
|
||||||
),
|
),
|
||||||
|
"credit_in_transaction_currency": flt(
|
||||||
|
amount, tax.precision("tax_amount_after_discount_amount")
|
||||||
|
),
|
||||||
"cost_center": tax.cost_center,
|
"cost_center": tax.cost_center,
|
||||||
},
|
},
|
||||||
account_currency,
|
account_currency,
|
||||||
@@ -1319,6 +1330,7 @@ class SalesInvoice(SellingController):
|
|||||||
"against": self.customer,
|
"against": self.customer,
|
||||||
"debit": flt(self.total_taxes_and_charges),
|
"debit": flt(self.total_taxes_and_charges),
|
||||||
"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
|
"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
|
||||||
|
"debit_in_transaction_currency": flt(self.total_taxes_and_charges),
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
},
|
},
|
||||||
account_currency,
|
account_currency,
|
||||||
@@ -1417,6 +1429,7 @@ class SalesInvoice(SellingController):
|
|||||||
if account_currency == self.company_currency
|
if account_currency == self.company_currency
|
||||||
else flt(amount, item.precision("net_amount"))
|
else flt(amount, item.precision("net_amount"))
|
||||||
),
|
),
|
||||||
|
"credit_in_transaction_currency": flt(amount, item.precision("net_amount")),
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
},
|
},
|
||||||
@@ -1468,6 +1481,7 @@ class SalesInvoice(SellingController):
|
|||||||
+ cstr(self.loyalty_redemption_account)
|
+ cstr(self.loyalty_redemption_account)
|
||||||
+ " for the Loyalty Program",
|
+ " for the Loyalty Program",
|
||||||
"credit": self.loyalty_amount,
|
"credit": self.loyalty_amount,
|
||||||
|
"credit_in_transaction_currency": self.loyalty_amount,
|
||||||
"against_voucher": self.return_against if cint(self.is_return) else self.name,
|
"against_voucher": self.return_against if cint(self.is_return) else self.name,
|
||||||
"against_voucher_type": self.doctype,
|
"against_voucher_type": self.doctype,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
@@ -1482,6 +1496,7 @@ class SalesInvoice(SellingController):
|
|||||||
"cost_center": self.cost_center or self.loyalty_redemption_cost_center,
|
"cost_center": self.cost_center or self.loyalty_redemption_cost_center,
|
||||||
"against": self.customer,
|
"against": self.customer,
|
||||||
"debit": self.loyalty_amount,
|
"debit": self.loyalty_amount,
|
||||||
|
"debit_in_transaction_currency": self.loyalty_amount,
|
||||||
"remark": "Loyalty Points redeemed by the customer",
|
"remark": "Loyalty Points redeemed by the customer",
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
@@ -1515,6 +1530,7 @@ class SalesInvoice(SellingController):
|
|||||||
"credit_in_account_currency": payment_mode.base_amount
|
"credit_in_account_currency": payment_mode.base_amount
|
||||||
if self.party_account_currency == self.company_currency
|
if self.party_account_currency == self.company_currency
|
||||||
else payment_mode.amount,
|
else payment_mode.amount,
|
||||||
|
"credit_in_transaction_currency": payment_mode.amount,
|
||||||
"against_voucher": against_voucher,
|
"against_voucher": against_voucher,
|
||||||
"against_voucher_type": self.doctype,
|
"against_voucher_type": self.doctype,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
@@ -1534,6 +1550,7 @@ class SalesInvoice(SellingController):
|
|||||||
"debit_in_account_currency": payment_mode.base_amount
|
"debit_in_account_currency": payment_mode.base_amount
|
||||||
if payment_mode_account_currency == self.company_currency
|
if payment_mode_account_currency == self.company_currency
|
||||||
else payment_mode.amount,
|
else payment_mode.amount,
|
||||||
|
"debit_in_transaction_currency": payment_mode.amount,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
},
|
},
|
||||||
payment_mode_account_currency,
|
payment_mode_account_currency,
|
||||||
@@ -1562,6 +1579,7 @@ class SalesInvoice(SellingController):
|
|||||||
"debit_in_account_currency": flt(self.base_change_amount)
|
"debit_in_account_currency": flt(self.base_change_amount)
|
||||||
if self.party_account_currency == self.company_currency
|
if self.party_account_currency == self.company_currency
|
||||||
else flt(self.change_amount),
|
else flt(self.change_amount),
|
||||||
|
"debit_in_transaction_currency": flt(self.change_amount),
|
||||||
"against_voucher": self.return_against
|
"against_voucher": self.return_against
|
||||||
if cint(self.is_return) and self.return_against
|
if cint(self.is_return) and self.return_against
|
||||||
else self.name,
|
else self.name,
|
||||||
@@ -1577,6 +1595,7 @@ class SalesInvoice(SellingController):
|
|||||||
"account": self.account_for_change_amount,
|
"account": self.account_for_change_amount,
|
||||||
"against": self.customer,
|
"against": self.customer,
|
||||||
"credit": self.base_change_amount,
|
"credit": self.base_change_amount,
|
||||||
|
"credit_in_transaction_currency": self.change_amount,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
@@ -1606,6 +1625,9 @@ class SalesInvoice(SellingController):
|
|||||||
if self.party_account_currency == self.company_currency
|
if self.party_account_currency == self.company_currency
|
||||||
else flt(self.write_off_amount, self.precision("write_off_amount"))
|
else flt(self.write_off_amount, self.precision("write_off_amount"))
|
||||||
),
|
),
|
||||||
|
"credit_in_transaction_currency": flt(
|
||||||
|
self.write_off_amount, self.precision("write_off_amount")
|
||||||
|
),
|
||||||
"against_voucher": self.return_against if cint(self.is_return) else self.name,
|
"against_voucher": self.return_against if cint(self.is_return) else self.name,
|
||||||
"against_voucher_type": self.doctype,
|
"against_voucher_type": self.doctype,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
@@ -1626,6 +1648,9 @@ class SalesInvoice(SellingController):
|
|||||||
if write_off_account_currency == self.company_currency
|
if write_off_account_currency == self.company_currency
|
||||||
else flt(self.write_off_amount, self.precision("write_off_amount"))
|
else flt(self.write_off_amount, self.precision("write_off_amount"))
|
||||||
),
|
),
|
||||||
|
"debit_in_transaction_currency": flt(
|
||||||
|
self.write_off_amount, self.precision("write_off_amount")
|
||||||
|
),
|
||||||
"cost_center": self.cost_center or self.write_off_cost_center or default_cost_center,
|
"cost_center": self.cost_center or self.write_off_cost_center or default_cost_center,
|
||||||
},
|
},
|
||||||
write_off_account_currency,
|
write_off_account_currency,
|
||||||
@@ -1670,6 +1695,9 @@ class SalesInvoice(SellingController):
|
|||||||
"credit_in_account_currency": flt(
|
"credit_in_account_currency": flt(
|
||||||
self.rounding_adjustment, self.precision("rounding_adjustment")
|
self.rounding_adjustment, self.precision("rounding_adjustment")
|
||||||
),
|
),
|
||||||
|
"credit_in_transaction_currency": flt(
|
||||||
|
self.rounding_adjustment, self.precision("rounding_adjustment")
|
||||||
|
),
|
||||||
"credit": flt(
|
"credit": flt(
|
||||||
self.base_rounding_adjustment, self.precision("base_rounding_adjustment")
|
self.base_rounding_adjustment, self.precision("base_rounding_adjustment")
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4354,6 +4354,20 @@ class TestSalesInvoice(IntegrationTestCase):
|
|||||||
pos_return = make_sales_return(pos.name)
|
pos_return = make_sales_return(pos.name)
|
||||||
self.assertEqual(abs(pos_return.payments[0].amount), pos.payments[0].amount)
|
self.assertEqual(abs(pos_return.payments[0].amount), pos.payments[0].amount)
|
||||||
|
|
||||||
|
def test_prevents_fully_returned_invoice_with_zero_quantity(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import StockOverReturnError, make_return_doc
|
||||||
|
|
||||||
|
invoice = create_sales_invoice(qty=10)
|
||||||
|
|
||||||
|
return_doc = make_return_doc(invoice.doctype, invoice.name)
|
||||||
|
return_doc.items[0].qty = -10
|
||||||
|
return_doc.save().submit()
|
||||||
|
|
||||||
|
return_doc = make_return_doc(invoice.doctype, invoice.name)
|
||||||
|
return_doc.items[0].qty = 0
|
||||||
|
|
||||||
|
self.assertRaises(StockOverReturnError, return_doc.save)
|
||||||
|
|
||||||
|
|
||||||
def set_advance_flag(company, flag, default_account):
|
def set_advance_flag(company, flag, default_account):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
|
|||||||
@@ -106,6 +106,9 @@
|
|||||||
"delivery_note",
|
"delivery_note",
|
||||||
"dn_detail",
|
"dn_detail",
|
||||||
"delivered_qty",
|
"delivered_qty",
|
||||||
|
"column_break_vwhb",
|
||||||
|
"pos_invoice",
|
||||||
|
"pos_invoice_item",
|
||||||
"internal_transfer_section",
|
"internal_transfer_section",
|
||||||
"purchase_order",
|
"purchase_order",
|
||||||
"column_break_92",
|
"column_break_92",
|
||||||
@@ -631,6 +634,7 @@
|
|||||||
"fieldname": "serial_no",
|
"fieldname": "serial_no",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"label": "Serial No",
|
"label": "Serial No",
|
||||||
|
"no_copy": 1,
|
||||||
"oldfieldname": "serial_no",
|
"oldfieldname": "serial_no",
|
||||||
"oldfieldtype": "Small Text"
|
"oldfieldtype": "Small Text"
|
||||||
},
|
},
|
||||||
@@ -952,18 +956,42 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pos_invoice_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
|
"label": "POS Invoice Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_vwhb",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pos_invoice",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "POS Invoice",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "POS Invoice",
|
||||||
|
"print_hide": 1,
|
||||||
|
"search_index": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-11-25 16:27:33.287341",
|
"modified": "2025-03-07 10:25:30.275246",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
"naming_rule": "Random",
|
"naming_rule": "Random",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ class SalesInvoiceItem(Document):
|
|||||||
parent: DF.Data
|
parent: DF.Data
|
||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
parenttype: DF.Data
|
parenttype: DF.Data
|
||||||
|
pos_invoice: DF.Link | None
|
||||||
|
pos_invoice_item: DF.Data | None
|
||||||
price_list_rate: DF.Currency
|
price_list_rate: DF.Currency
|
||||||
pricing_rules: DF.SmallText | None
|
pricing_rules: DF.SmallText | None
|
||||||
project: DF.Link | None
|
project: DF.Link | None
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ frappe.ui.form.on("Tax Withholding Category", {
|
|||||||
filters: {
|
filters: {
|
||||||
company: child.company,
|
company: child.company,
|
||||||
root_type: ["in", ["Asset", "Liability"]],
|
root_type: ["in", ["Asset", "Liability"]],
|
||||||
|
is_group: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,27 +36,38 @@ class TaxWithholdingCategory(Document):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
self.validate_accounts()
|
self.validate_companies_and_accounts()
|
||||||
self.validate_thresholds()
|
self.validate_thresholds()
|
||||||
|
|
||||||
def validate_dates(self):
|
def validate_dates(self):
|
||||||
last_date = None
|
last_to_date = None
|
||||||
for d in self.get("rates"):
|
rates = sorted(self.get("rates"), key=lambda d: getdate(d.from_date))
|
||||||
|
|
||||||
|
for d in rates:
|
||||||
if getdate(d.from_date) >= getdate(d.to_date):
|
if getdate(d.from_date) >= getdate(d.to_date):
|
||||||
frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx))
|
frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx))
|
||||||
|
|
||||||
# validate overlapping of dates
|
# validate overlapping of dates
|
||||||
if last_date and getdate(d.to_date) < getdate(last_date):
|
if last_to_date and getdate(d.from_date) < getdate(last_to_date):
|
||||||
frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx))
|
frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx))
|
||||||
|
|
||||||
def validate_accounts(self):
|
last_to_date = d.to_date
|
||||||
existing_accounts = []
|
|
||||||
|
def validate_companies_and_accounts(self):
|
||||||
|
existing_accounts = set()
|
||||||
|
companies = set()
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
|
# validate duplicate company
|
||||||
|
if d.get("company") in companies:
|
||||||
|
frappe.throw(_("Company {0} added multiple times").format(frappe.bold(d.get("company"))))
|
||||||
|
companies.add(d.get("company"))
|
||||||
|
|
||||||
|
# validate duplicate account
|
||||||
if d.get("account") in existing_accounts:
|
if d.get("account") in existing_accounts:
|
||||||
frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get("account"))))
|
frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get("account"))))
|
||||||
|
|
||||||
validate_account_head(d.idx, d.get("account"), d.get("company"))
|
validate_account_head(d.idx, d.get("account"), d.get("company"))
|
||||||
existing_accounts.append(d.get("account"))
|
existing_accounts.add(d.get("account"))
|
||||||
|
|
||||||
def validate_thresholds(self):
|
def validate_thresholds(self):
|
||||||
for d in self.get("rates"):
|
for d in self.get("rates"):
|
||||||
|
|||||||
@@ -529,7 +529,7 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
|
|||||||
payment = get_payment_entry(order.doctype, order.name)
|
payment = get_payment_entry(order.doctype, order.name)
|
||||||
payment.apply_tax_withholding_amount = 1
|
payment.apply_tax_withholding_amount = 1
|
||||||
payment.tax_withholding_category = "Cumulative Threshold TDS"
|
payment.tax_withholding_category = "Cumulative Threshold TDS"
|
||||||
payment.submit()
|
payment.save().submit()
|
||||||
self.assertEqual(payment.taxes[0].tax_amount, 4000)
|
self.assertEqual(payment.taxes[0].tax_amount, 4000)
|
||||||
|
|
||||||
def test_multi_category_single_supplier(self):
|
def test_multi_category_single_supplier(self):
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ def process_debit_credit_difference(gl_map):
|
|||||||
voucher_no = gl_map[0].voucher_no
|
voucher_no = gl_map[0].voucher_no
|
||||||
allowance = get_debit_credit_allowance(voucher_type, precision)
|
allowance = get_debit_credit_allowance(voucher_type, precision)
|
||||||
|
|
||||||
debit_credit_diff = get_debit_credit_difference(gl_map, precision)
|
debit_credit_diff, trx_cur_debit_credit_diff = get_debit_credit_difference(gl_map, precision)
|
||||||
|
|
||||||
if abs(debit_credit_diff) > allowance:
|
if abs(debit_credit_diff) > allowance:
|
||||||
if not (
|
if not (
|
||||||
@@ -442,9 +442,9 @@ def process_debit_credit_difference(gl_map):
|
|||||||
raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
|
raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
|
||||||
|
|
||||||
elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
|
elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
|
||||||
make_round_off_gle(gl_map, debit_credit_diff, precision)
|
make_round_off_gle(gl_map, debit_credit_diff, trx_cur_debit_credit_diff, precision)
|
||||||
|
|
||||||
debit_credit_diff = get_debit_credit_difference(gl_map, precision)
|
debit_credit_diff, trx_cur_debit_credit_diff = get_debit_credit_difference(gl_map, precision)
|
||||||
if abs(debit_credit_diff) > allowance:
|
if abs(debit_credit_diff) > allowance:
|
||||||
if not (
|
if not (
|
||||||
voucher_type == "Journal Entry"
|
voucher_type == "Journal Entry"
|
||||||
@@ -456,14 +456,23 @@ def process_debit_credit_difference(gl_map):
|
|||||||
|
|
||||||
def get_debit_credit_difference(gl_map, precision):
|
def get_debit_credit_difference(gl_map, precision):
|
||||||
debit_credit_diff = 0.0
|
debit_credit_diff = 0.0
|
||||||
|
trx_cur_debit_credit_diff = 0
|
||||||
|
|
||||||
for entry in gl_map:
|
for entry in gl_map:
|
||||||
entry.debit = flt(entry.debit, precision)
|
entry.debit = flt(entry.debit, precision)
|
||||||
entry.credit = flt(entry.credit, precision)
|
entry.credit = flt(entry.credit, precision)
|
||||||
debit_credit_diff += entry.debit - entry.credit
|
debit_credit_diff += entry.debit - entry.credit
|
||||||
|
|
||||||
debit_credit_diff = flt(debit_credit_diff, precision)
|
entry.debit_in_transaction_currency = flt(entry.debit_in_transaction_currency, precision)
|
||||||
|
entry.credit_in_transaction_currency = flt(entry.credit_in_transaction_currency, precision)
|
||||||
|
trx_cur_debit_credit_diff += (
|
||||||
|
entry.debit_in_transaction_currency - entry.credit_in_transaction_currency
|
||||||
|
)
|
||||||
|
|
||||||
return debit_credit_diff
|
debit_credit_diff = flt(debit_credit_diff, precision)
|
||||||
|
trx_cur_debit_credit_diff = flt(trx_cur_debit_credit_diff, precision)
|
||||||
|
|
||||||
|
return debit_credit_diff, trx_cur_debit_credit_diff
|
||||||
|
|
||||||
|
|
||||||
def get_debit_credit_allowance(voucher_type, precision):
|
def get_debit_credit_allowance(voucher_type, precision):
|
||||||
@@ -490,7 +499,7 @@ def has_opening_entries(gl_map: list) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
def make_round_off_gle(gl_map, debit_credit_diff, trx_cur_debit_credit_diff, precision):
|
||||||
round_off_account, round_off_cost_center, round_off_for_opening = get_round_off_account_and_cost_center(
|
round_off_account, round_off_cost_center, round_off_for_opening = get_round_off_account_and_cost_center(
|
||||||
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
|
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
|
||||||
)
|
)
|
||||||
@@ -535,6 +544,12 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
|||||||
"credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
|
"credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
|
||||||
"debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
|
"debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
|
||||||
"credit": debit_credit_diff if debit_credit_diff > 0 else 0,
|
"credit": debit_credit_diff if debit_credit_diff > 0 else 0,
|
||||||
|
"debit_in_transaction_currency": abs(trx_cur_debit_credit_diff)
|
||||||
|
if trx_cur_debit_credit_diff < 0
|
||||||
|
else 0,
|
||||||
|
"credit_in_transaction_currency": trx_cur_debit_credit_diff
|
||||||
|
if trx_cur_debit_credit_diff > 0
|
||||||
|
else 0,
|
||||||
"cost_center": round_off_cost_center,
|
"cost_center": round_off_cost_center,
|
||||||
"party_type": None,
|
"party_type": None,
|
||||||
"party": None,
|
"party": None,
|
||||||
|
|||||||
@@ -577,12 +577,13 @@ def validate_party_accounts(doc):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_due_date(posting_date, party_type, party, company=None, bill_date=None):
|
def get_due_date(posting_date, party_type, party, company=None, bill_date=None, template_name=None):
|
||||||
"""Get due date from `Payment Terms Template`"""
|
"""Get due date from `Payment Terms Template`"""
|
||||||
due_date = None
|
due_date = None
|
||||||
if (bill_date or posting_date) and party:
|
if (bill_date or posting_date) and party:
|
||||||
due_date = bill_date or posting_date
|
due_date = bill_date or posting_date
|
||||||
template_name = get_payment_terms_template(party, party_type, company)
|
if not template_name:
|
||||||
|
template_name = get_payment_terms_template(party, party_type, company)
|
||||||
|
|
||||||
if template_name:
|
if template_name:
|
||||||
due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d")
|
due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d")
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
{{ doc.address_display }}
|
{{ doc.address_display }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
{{ _("Conatct: ")+doc.contact_display if doc.contact_display else '' }}
|
{{ _("Contact: ")+doc.contact_display if doc.contact_display else '' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
{{ _("Mobile: ")+doc.contact_mobile if doc.contact_mobile else '' }}
|
{{ _("Mobile: ")+doc.contact_mobile if doc.contact_mobile else '' }}
|
||||||
|
|||||||
@@ -38,23 +38,6 @@ class TestAccountsPayable(AccountsTestMixin, IntegrationTestCase):
|
|||||||
self.assertEqual(data[1][0].get("outstanding"), 300)
|
self.assertEqual(data[1][0].get("outstanding"), 300)
|
||||||
self.assertEqual(data[1][0].get("currency"), "USD")
|
self.assertEqual(data[1][0].get("currency"), "USD")
|
||||||
|
|
||||||
def test_account_payable_for_debit_note(self):
|
|
||||||
pi = self.create_purchase_invoice(do_not_submit=True)
|
|
||||||
pi.is_return = 1
|
|
||||||
pi.items[0].qty = -1
|
|
||||||
pi = pi.save().submit()
|
|
||||||
|
|
||||||
filters = {
|
|
||||||
"company": self.company,
|
|
||||||
"party_type": "Supplier",
|
|
||||||
"party": [self.supplier],
|
|
||||||
"report_date": today(),
|
|
||||||
"range": "30, 60, 90, 120",
|
|
||||||
}
|
|
||||||
|
|
||||||
data = execute(filters)
|
|
||||||
self.assertEqual(data[1][0].get("invoiced"), 300)
|
|
||||||
|
|
||||||
def create_purchase_invoice(self, do_not_submit=False):
|
def create_purchase_invoice(self, do_not_submit=False):
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
pi = make_purchase_invoice(
|
pi = make_purchase_invoice(
|
||||||
|
|||||||
@@ -267,18 +267,6 @@ class ReceivablePayableReport:
|
|||||||
row.invoiced_in_account_currency += amount_in_account_currency
|
row.invoiced_in_account_currency += amount_in_account_currency
|
||||||
else:
|
else:
|
||||||
if self.is_invoice(ple):
|
if self.is_invoice(ple):
|
||||||
# when invoice has is_return marked
|
|
||||||
if self.invoice_details.get(row.voucher_no, {}).get("is_return"):
|
|
||||||
# for Credit Note
|
|
||||||
if row.voucher_type == "Sales Invoice":
|
|
||||||
row.credit_note -= amount
|
|
||||||
row.credit_note_in_account_currency -= amount_in_account_currency
|
|
||||||
# for Debit Note
|
|
||||||
else:
|
|
||||||
row.invoiced -= amount
|
|
||||||
row.invoiced_in_account_currency -= amount_in_account_currency
|
|
||||||
return
|
|
||||||
|
|
||||||
if row.voucher_no == ple.voucher_no == ple.against_voucher_no:
|
if row.voucher_no == ple.voucher_no == ple.against_voucher_no:
|
||||||
row.paid -= amount
|
row.paid -= amount
|
||||||
row.paid_in_account_currency -= amount_in_account_currency
|
row.paid_in_account_currency -= amount_in_account_currency
|
||||||
@@ -433,7 +421,7 @@ class ReceivablePayableReport:
|
|||||||
# nosemgrep
|
# nosemgrep
|
||||||
si_list = frappe.db.sql(
|
si_list = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select name, due_date, po_no, is_return
|
select name, due_date, po_no
|
||||||
from `tabSales Invoice`
|
from `tabSales Invoice`
|
||||||
where posting_date <= %s
|
where posting_date <= %s
|
||||||
and company = %s
|
and company = %s
|
||||||
@@ -465,7 +453,7 @@ class ReceivablePayableReport:
|
|||||||
# nosemgrep
|
# nosemgrep
|
||||||
for pi in frappe.db.sql(
|
for pi in frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select name, due_date, bill_no, bill_date, is_return
|
select name, due_date, bill_no, bill_date
|
||||||
from `tabPurchase Invoice`
|
from `tabPurchase Invoice`
|
||||||
where
|
where
|
||||||
posting_date <= %s
|
posting_date <= %s
|
||||||
@@ -532,7 +520,7 @@ class ReceivablePayableReport:
|
|||||||
ps.description, ps.paid_amount, ps.discounted_amount
|
ps.description, ps.paid_amount, ps.discounted_amount
|
||||||
from `tab{row.voucher_type}` si, `tabPayment Schedule` ps
|
from `tab{row.voucher_type}` si, `tabPayment Schedule` ps
|
||||||
where
|
where
|
||||||
si.name = ps.parent and
|
si.name = ps.parent and ps.parenttype = '{row.voucher_type}' and
|
||||||
si.name = %s and
|
si.name = %s and
|
||||||
si.is_return = 0
|
si.is_return = 0
|
||||||
order by ps.paid_amount desc, due_date
|
order by ps.paid_amount desc, due_date
|
||||||
@@ -741,11 +729,13 @@ class ReceivablePayableReport:
|
|||||||
"company": self.filters.company,
|
"company": self.filters.company,
|
||||||
"update_outstanding_for_self": 0,
|
"update_outstanding_for_self": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
or_filters = {}
|
or_filters = {}
|
||||||
for party_type in self.party_type:
|
if party_type := self.filters.party_type:
|
||||||
party_field = scrub(party_type)
|
party_field = scrub(party_type)
|
||||||
if self.filters.get(party_field):
|
if parties := self.filters.get("party"):
|
||||||
or_filters.update({party_field: self.filters.get(party_field)})
|
or_filters.update({party_field: ["in", parties]})
|
||||||
|
|
||||||
self.return_entries = frappe._dict(
|
self.return_entries = frappe._dict(
|
||||||
frappe.get_all(
|
frappe.get_all(
|
||||||
doctype, filters=filters, or_filters=or_filters, fields=["name", "return_against"], as_list=1
|
doctype, filters=filters, or_filters=or_filters, fields=["name", "return_against"], as_list=1
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase):
|
|||||||
|
|
||||||
expected_data_after_credit_note = [
|
expected_data_after_credit_note = [
|
||||||
[100.0, 100.0, 40.0, 0.0, 60.0, si.name],
|
[100.0, 100.0, 40.0, 0.0, 60.0, si.name],
|
||||||
[0, 0, 0, 100.0, -100.0, cr_note.name],
|
[0, 0, 100.0, 0.0, -100.0, cr_note.name],
|
||||||
]
|
]
|
||||||
self.assertEqual(len(report[1]), 2)
|
self.assertEqual(len(report[1]), 2)
|
||||||
si_row = next(
|
si_row = next(
|
||||||
@@ -478,19 +478,13 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase):
|
|||||||
report = execute(filters)[1]
|
report = execute(filters)[1]
|
||||||
self.assertEqual(len(report), 2)
|
self.assertEqual(len(report), 2)
|
||||||
|
|
||||||
expected_data = {sr.name: [0.0, 10.0, -10.0, 0.0, -10], si.name: [100.0, 0.0, 100.0, 10.0, 90.0]}
|
expected_data = {sr.name: [10.0, -10.0, 0.0, -10], si.name: [100.0, 100.0, 10.0, 90.0]}
|
||||||
|
|
||||||
rows = report[:2]
|
rows = report[:2]
|
||||||
for row in rows:
|
for row in rows:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
expected_data[row.voucher_no],
|
expected_data[row.voucher_no],
|
||||||
[
|
[row.invoiced or row.paid, row.outstanding, row.remaining_balance, row.future_amount],
|
||||||
row.invoiced or row.paid,
|
|
||||||
row.credit_note,
|
|
||||||
row.outstanding,
|
|
||||||
row.remaining_balance,
|
|
||||||
row.future_amount,
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
pe.cancel()
|
pe.cancel()
|
||||||
|
|||||||
@@ -473,6 +473,16 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onload() {
|
||||||
|
this.frm.set_query("supplier", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_transporter: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get_items_from_open_material_requests() {
|
get_items_from_open_material_requests() {
|
||||||
erpnext.utils.map_current_doc({
|
erpnext.utils.map_current_doc({
|
||||||
method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order_based_on_supplier",
|
method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order_based_on_supplier",
|
||||||
|
|||||||
@@ -933,6 +933,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
|
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||||
"fieldname": "subcontracted_quantity",
|
"fieldname": "subcontracted_quantity",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Subcontracted Quantity",
|
"label": "Subcontracted Quantity",
|
||||||
@@ -946,7 +947,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-03-02 16:58:26.059601",
|
"modified": "2025-03-13 17:27:43.468602",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item",
|
"name": "Purchase Order Item",
|
||||||
@@ -960,4 +961,4 @@
|
|||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,6 +274,7 @@ class AccountsController(TransactionBase):
|
|||||||
self.set_total_in_words()
|
self.set_total_in_words()
|
||||||
self.set_default_letter_head()
|
self.set_default_letter_head()
|
||||||
self.validate_company_in_accounting_dimension()
|
self.validate_company_in_accounting_dimension()
|
||||||
|
self.validate_party_address_and_contact()
|
||||||
|
|
||||||
def set_default_letter_head(self):
|
def set_default_letter_head(self):
|
||||||
if hasattr(self, "letter_head") and not self.letter_head:
|
if hasattr(self, "letter_head") and not self.letter_head:
|
||||||
@@ -445,6 +446,45 @@ class AccountsController(TransactionBase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_party_address_and_contact(self):
|
||||||
|
party, party_type = None, None
|
||||||
|
if self.get("customer"):
|
||||||
|
party, party_type = self.customer, "Customer"
|
||||||
|
billing_address, shipping_address = (
|
||||||
|
self.get("customer_address"),
|
||||||
|
self.get("shipping_address_name"),
|
||||||
|
)
|
||||||
|
self.validate_party_address(party, party_type, billing_address, shipping_address)
|
||||||
|
elif self.get("supplier"):
|
||||||
|
party, party_type = self.supplier, "Supplier"
|
||||||
|
billing_address = self.get("supplier_address")
|
||||||
|
self.validate_party_address(party, party_type, billing_address)
|
||||||
|
|
||||||
|
if party and party_type:
|
||||||
|
self.validate_party_contact(party, party_type)
|
||||||
|
|
||||||
|
def validate_party_address(self, party, party_type, billing_address, shipping_address=None):
|
||||||
|
if billing_address or shipping_address:
|
||||||
|
party_address = frappe.get_all(
|
||||||
|
"Dynamic Link",
|
||||||
|
{"link_doctype": party_type, "link_name": party, "parenttype": "Address"},
|
||||||
|
pluck="parent",
|
||||||
|
)
|
||||||
|
if billing_address and billing_address not in party_address:
|
||||||
|
frappe.throw(_("Billing Address does not belong to the {0}").format(party))
|
||||||
|
elif shipping_address and shipping_address not in party_address:
|
||||||
|
frappe.throw(_("Shipping Address does not belong to the {0}").format(party))
|
||||||
|
|
||||||
|
def validate_party_contact(self, party, party_type):
|
||||||
|
if self.get("contact_person"):
|
||||||
|
contact = frappe.get_all(
|
||||||
|
"Dynamic Link",
|
||||||
|
{"link_doctype": party_type, "link_name": party, "parenttype": "Contact"},
|
||||||
|
pluck="parent",
|
||||||
|
)
|
||||||
|
if self.contact_person and self.contact_person not in contact:
|
||||||
|
frappe.throw(_("Contact Person does not belong to the {0}").format(party))
|
||||||
|
|
||||||
def validate_return_against_account(self):
|
def validate_return_against_account(self):
|
||||||
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
|
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
|
||||||
cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to"
|
cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to"
|
||||||
@@ -1125,20 +1165,19 @@ class AccountsController(TransactionBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Update details in transaction currency
|
# Update details in transaction currency
|
||||||
gl_dict.update(
|
if self.doctype not in ["Purchase Invoice", "Sales Invoice", "Journal Entry", "Payment Entry"]:
|
||||||
{
|
gl_dict.update(
|
||||||
"transaction_currency": self.get("currency") or self.company_currency,
|
{
|
||||||
"transaction_exchange_rate": item.get("exchange_rate", 1)
|
"transaction_currency": self.get("currency") or self.company_currency,
|
||||||
if self.doctype == "Journal Entry" and item
|
"transaction_exchange_rate": self.get("conversion_rate", 1),
|
||||||
else self.get("conversion_rate", 1),
|
"debit_in_transaction_currency": self.get_value_in_transaction_currency(
|
||||||
"debit_in_transaction_currency": self.get_value_in_transaction_currency(
|
account_currency, gl_dict, "debit"
|
||||||
account_currency, gl_dict, "debit"
|
),
|
||||||
),
|
"credit_in_transaction_currency": self.get_value_in_transaction_currency(
|
||||||
"credit_in_transaction_currency": self.get_value_in_transaction_currency(
|
account_currency, gl_dict, "credit"
|
||||||
account_currency, gl_dict, "credit"
|
),
|
||||||
),
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if not args.get("against_voucher_type") and self.get("against_voucher_type"):
|
if not args.get("against_voucher_type") and self.get("against_voucher_type"):
|
||||||
gl_dict.update({"against_voucher_type": self.get("against_voucher_type")})
|
gl_dict.update({"against_voucher_type": self.get("against_voucher_type")})
|
||||||
@@ -2782,6 +2821,11 @@ class AccountsController(TransactionBase):
|
|||||||
elif self.doctype == "Payment Entry":
|
elif self.doctype == "Payment Entry":
|
||||||
self.make_advance_payment_ledger_for_payment()
|
self.make_advance_payment_ledger_for_payment()
|
||||||
|
|
||||||
|
def set_transaction_currency_and_rate_in_gl_map(self, gl_entries):
|
||||||
|
for x in gl_entries:
|
||||||
|
x["transaction_currency"] = self.currency
|
||||||
|
x["transaction_exchange_rate"] = self.get("conversion_rate") or 1
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_tax_rate(account_head):
|
def get_tax_rate(account_head):
|
||||||
|
|||||||
@@ -224,14 +224,18 @@ class BuyingController(SubcontractingController):
|
|||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.get("from_warehouse") and (item.get("from_warehouse") == item.get("warehouse")):
|
if item.get("from_warehouse") and (item.get("from_warehouse") == item.get("warehouse")):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx)
|
_("Row #{idx}: {from_warehouse_field} and {to_warehouse_field} cannot be same.").format(
|
||||||
|
idx=item.idx,
|
||||||
|
from_warehouse_field=_(item.meta.get_label("from_warehouse")),
|
||||||
|
to_warehouse_field=_(item.meta.get_label("warehouse")),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if item.get("from_warehouse") and self.get("is_subcontracted"):
|
if item.get("from_warehouse") and self.get("is_subcontracted"):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor"
|
"Row #{idx}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor."
|
||||||
).format(item.idx)
|
).format(idx=item.idx)
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_supplier_address(self):
|
def set_supplier_address(self):
|
||||||
@@ -376,8 +380,8 @@ class BuyingController(SubcontractingController):
|
|||||||
d.rate = d.sales_incoming_rate
|
d.rate = d.sales_incoming_rate
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_(
|
_(
|
||||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
"Row #{idx}: Item rate has been updated as per valuation rate since its an internal stock transfer."
|
||||||
).format(d.idx),
|
).format(idx=d.idx),
|
||||||
alert=1,
|
alert=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -427,17 +431,28 @@ class BuyingController(SubcontractingController):
|
|||||||
def validate_for_subcontracting(self):
|
def validate_for_subcontracting(self):
|
||||||
if self.is_subcontracted and self.get("is_old_subcontracting_flow"):
|
if self.is_subcontracted and self.get("is_old_subcontracting_flow"):
|
||||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
|
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
|
||||||
frappe.throw(_("Supplier Warehouse mandatory for sub-contracted {0}").format(self.doctype))
|
frappe.throw(
|
||||||
|
_("{field_label} is mandatory for sub-contracted {doctype}.").format(
|
||||||
|
field_label=_(self.meta.get_label("supplier_warehouse")), doctype=_(self.doctype)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item in self.sub_contracted_items and not item.bom:
|
if item in self.sub_contracted_items and not item.bom:
|
||||||
frappe.throw(_("Please select BOM in BOM field for Item {0}").format(item.item_code))
|
frappe.throw(
|
||||||
|
_("Please select BOM in BOM field for Item {item_code}.").format(
|
||||||
|
item_code=frappe.bold(item.item_code)
|
||||||
|
)
|
||||||
|
)
|
||||||
if self.doctype != "Purchase Order":
|
if self.doctype != "Purchase Order":
|
||||||
return
|
return
|
||||||
for row in self.get("supplied_items"):
|
for row in self.get("supplied_items"):
|
||||||
if not row.reserve_warehouse:
|
if not row.reserve_warehouse:
|
||||||
msg = f"Reserved Warehouse is mandatory for the Item {frappe.bold(row.rm_item_code)} in Raw Materials supplied"
|
frappe.throw(
|
||||||
frappe.throw(_(msg))
|
_(
|
||||||
|
"Reserved Warehouse is mandatory for the Item {item_code} in Raw Materials supplied."
|
||||||
|
).format(item_code=frappe.bold(row.rm_item_code))
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.get("bom"):
|
if item.get("bom"):
|
||||||
@@ -453,7 +468,12 @@ class BuyingController(SubcontractingController):
|
|||||||
# Check if item code is present
|
# Check if item code is present
|
||||||
# Conversion factor should not be mandatory for non itemized items
|
# Conversion factor should not be mandatory for non itemized items
|
||||||
if not d.conversion_factor and d.item_code:
|
if not d.conversion_factor and d.item_code:
|
||||||
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
|
frappe.throw(
|
||||||
|
_("Row #{idx}: {field_label} is mandatory.").format(
|
||||||
|
idx=d.idx,
|
||||||
|
field_label=_(d.meta.get_label("conversion_factor")),
|
||||||
|
)
|
||||||
|
)
|
||||||
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
|
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
|
||||||
|
|
||||||
if self.doctype == "Purchase Receipt" and d.meta.get_field("received_stock_qty"):
|
if self.doctype == "Purchase Receipt" and d.meta.get_field("received_stock_qty"):
|
||||||
@@ -470,7 +490,12 @@ class BuyingController(SubcontractingController):
|
|||||||
def validate_purchase_return(self):
|
def validate_purchase_return(self):
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if self.is_return and flt(d.rejected_qty) != 0:
|
if self.is_return and flt(d.rejected_qty) != 0:
|
||||||
frappe.throw(_("Row #{0}: Rejected Qty can not be entered in Purchase Return").format(d.idx))
|
frappe.throw(
|
||||||
|
_("Row #{idx}: {field_label} is not allowed in Purchase Return.").format(
|
||||||
|
idx=d.idx,
|
||||||
|
field_label=_(d.meta.get_label("rejected_qty")),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# validate rate with ref PR
|
# validate rate with ref PR
|
||||||
|
|
||||||
@@ -486,8 +511,8 @@ class BuyingController(SubcontractingController):
|
|||||||
val = flt(d.qty) + flt(d.rejected_qty)
|
val = flt(d.qty) + flt(d.rejected_qty)
|
||||||
if flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty")):
|
if flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty")):
|
||||||
message = _(
|
message = _(
|
||||||
"Row #{0}: Received Qty must be equal to Accepted + Rejected Qty for Item {1}"
|
"Row #{idx}: Received Qty must be equal to Accepted + Rejected Qty for Item {item_code}."
|
||||||
).format(d.idx, d.item_code)
|
).format(idx=d.idx, item_code=frappe.bold(d.item_code))
|
||||||
frappe.throw(msg=message, title=_("Mismatch"), exc=QtyMismatchError)
|
frappe.throw(msg=message, title=_("Mismatch"), exc=QtyMismatchError)
|
||||||
|
|
||||||
def validate_negative_quantity(self, item_row, field_list):
|
def validate_negative_quantity(self, item_row, field_list):
|
||||||
@@ -498,10 +523,10 @@ class BuyingController(SubcontractingController):
|
|||||||
for fieldname in field_list:
|
for fieldname in field_list:
|
||||||
if flt(item_row[fieldname]) < 0:
|
if flt(item_row[fieldname]) < 0:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0}: {1} can not be negative for item {2}").format(
|
_("Row #{idx}: {field_label} can not be negative for item {item_code}.").format(
|
||||||
item_row["idx"],
|
idx=item_row["idx"],
|
||||||
frappe.get_meta(item_row.doctype).get_label(fieldname),
|
field_label=frappe.get_meta(item_row.doctype).get_label(fieldname),
|
||||||
item_row["item_code"],
|
item_code=frappe.bold(item_row["item_code"]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -510,7 +535,13 @@ class BuyingController(SubcontractingController):
|
|||||||
if d.get(ref_fieldname):
|
if d.get(ref_fieldname):
|
||||||
status = frappe.db.get_value(ref_doctype, d.get(ref_fieldname), "status")
|
status = frappe.db.get_value(ref_doctype, d.get(ref_fieldname), "status")
|
||||||
if status in ("Closed", "On Hold"):
|
if status in ("Closed", "On Hold"):
|
||||||
frappe.throw(_("{0} {1} is {2}").format(ref_doctype, d.get(ref_fieldname), status))
|
frappe.throw(
|
||||||
|
_("{ref_doctype} {ref_name} is {status}.").format(
|
||||||
|
ref_doctype=frappe.bold(_(ref_doctype)),
|
||||||
|
ref_name=frappe.bold(d.get(ref_fieldname)),
|
||||||
|
status=frappe.bold(_(status)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
|
def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||||
self.update_ordered_and_reserved_qty()
|
self.update_ordered_and_reserved_qty()
|
||||||
@@ -675,7 +706,10 @@ class BuyingController(SubcontractingController):
|
|||||||
|
|
||||||
if po_obj.status in ["Closed", "Cancelled"]:
|
if po_obj.status in ["Closed", "Cancelled"]:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("{0} {1} is cancelled or closed").format(_("Purchase Order"), po),
|
_("{doctype} {name} is cancelled or closed.").format(
|
||||||
|
doctype=frappe.bold(_("Purchase Order")),
|
||||||
|
name=frappe.bold(po),
|
||||||
|
),
|
||||||
frappe.InvalidStatusError,
|
frappe.InvalidStatusError,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -768,8 +802,8 @@ class BuyingController(SubcontractingController):
|
|||||||
if len(created_assets) > 5:
|
if len(created_assets) > 5:
|
||||||
# dont show asset form links if more than 5 assets are created
|
# dont show asset form links if more than 5 assets are created
|
||||||
messages.append(
|
messages.append(
|
||||||
_("{} Assets created for {}").format(
|
_("{count} Assets created for {item_code}").format(
|
||||||
len(created_assets), frappe.bold(d.item_code)
|
count=len(created_assets), item_code=frappe.bold(d.item_code)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -778,25 +812,28 @@ class BuyingController(SubcontractingController):
|
|||||||
)
|
)
|
||||||
assets_link = frappe.bold(",".join(assets_link))
|
assets_link = frappe.bold(",".join(assets_link))
|
||||||
|
|
||||||
is_plural = "s" if len(created_assets) != 1 else ""
|
if len(created_assets) == 1:
|
||||||
messages.append(
|
msg = _("Asset {assets_link} created for {item_code}").format(
|
||||||
_("Asset{is_plural} {assets_link} created for {item_code}").format(
|
|
||||||
is_plural=is_plural,
|
|
||||||
assets_link=assets_link,
|
assets_link=assets_link,
|
||||||
item_code=frappe.bold(d.item_code),
|
item_code=frappe.bold(d.item_code),
|
||||||
)
|
)
|
||||||
)
|
else:
|
||||||
|
msg = _("Assets {assets_link} created for {item_code}").format(
|
||||||
|
assets_link=assets_link,
|
||||||
|
item_code=frappe.bold(d.item_code),
|
||||||
|
)
|
||||||
|
messages.append(msg)
|
||||||
else:
|
else:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Row {}: Asset Naming Series is mandatory for the auto creation for item {}"
|
"Row {idx}: Asset Naming Series is mandatory for the auto creation of assets for item {item_code}."
|
||||||
).format(d.idx, frappe.bold(d.item_code))
|
).format(idx=d.idx, item_code=frappe.bold(d.item_code))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
messages.append(
|
messages.append(
|
||||||
_("Assets not created for {0}. You will have to create asset manually.").format(
|
_(
|
||||||
frappe.bold(d.item_code)
|
"Assets not created for {item_code}. You will have to create asset manually."
|
||||||
)
|
).format(item_code=frappe.bold(d.item_code))
|
||||||
)
|
)
|
||||||
alert = True
|
alert = True
|
||||||
|
|
||||||
@@ -805,7 +842,12 @@ class BuyingController(SubcontractingController):
|
|||||||
|
|
||||||
def make_asset(self, row, accounting_dimensions, is_grouped_asset=False):
|
def make_asset(self, row, accounting_dimensions, is_grouped_asset=False):
|
||||||
if not row.asset_location:
|
if not row.asset_location:
|
||||||
frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code))
|
frappe.throw(
|
||||||
|
_("Row #{idx}: Please enter a location for the asset item {item_code}.").format(
|
||||||
|
idx=row.idx,
|
||||||
|
item_code=frappe.bold(row.item_code),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
item_data = frappe.get_cached_value(
|
item_data = frappe.get_cached_value(
|
||||||
"Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1
|
"Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1
|
||||||
@@ -880,8 +922,8 @@ class BuyingController(SubcontractingController):
|
|||||||
if asset.docstatus == 1 and delete_asset:
|
if asset.docstatus == 1 and delete_asset:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Cannot cancel this document as it is linked with submitted asset {0}. Please cancel it to continue."
|
"Cannot cancel this document as it is linked with the submitted asset {asset_link}. Please cancel the asset to continue."
|
||||||
).format(frappe.utils.get_link_to_form("Asset", asset.name))
|
).format(asset_link=frappe.utils.get_link_to_form("Asset", asset.name))
|
||||||
)
|
)
|
||||||
|
|
||||||
asset.flags.ignore_validate_update_after_submit = True
|
asset.flags.ignore_validate_update_after_submit = True
|
||||||
@@ -918,9 +960,19 @@ class BuyingController(SubcontractingController):
|
|||||||
and self.transaction_date
|
and self.transaction_date
|
||||||
and getdate(d.schedule_date) < getdate(self.transaction_date)
|
and getdate(d.schedule_date) < getdate(self.transaction_date)
|
||||||
):
|
):
|
||||||
frappe.throw(_("Row #{0}: Reqd by Date cannot be before Transaction Date").format(d.idx))
|
frappe.throw(
|
||||||
|
_("Row #{idx}: {schedule_date} cannot be before {transaction_date}.").format(
|
||||||
|
idx=d.idx,
|
||||||
|
schedule_date=_(self.meta.get_label("schedule_date")),
|
||||||
|
transaction_date=_(self.meta.get_label("transaction_date")),
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("Please enter Reqd by Date"))
|
frappe.throw(
|
||||||
|
_("Please enter the {schedule_date}.").format(
|
||||||
|
schedule_date=_(self.meta.get_label("schedule_date"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_items(self):
|
def validate_items(self):
|
||||||
# validate items to see if they have is_purchase_item or is_subcontracted_item enabled
|
# validate items to see if they have is_purchase_item or is_subcontracted_item enabled
|
||||||
@@ -970,12 +1022,18 @@ def validate_item_type(doc, fieldname, message):
|
|||||||
|
|
||||||
if len(invalid_items) > 1:
|
if len(invalid_items) > 1:
|
||||||
error_message = _(
|
error_message = _(
|
||||||
"Following items {0} are not marked as {1} item. You can enable them as {1} item from its Item master"
|
"The items {items} are not marked as {type_of} item. You can enable them as {type_of} item from their Item masters."
|
||||||
).format(items, message)
|
).format(
|
||||||
|
items=items,
|
||||||
|
type_of=message,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
error_message = _(
|
error_message = _(
|
||||||
"Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master"
|
"The item {item} is not marked as {type_of} item. You can enable it as {type_of} item from its Item master."
|
||||||
).format(items, message)
|
).format(
|
||||||
|
item=items,
|
||||||
|
type_of=message,
|
||||||
|
)
|
||||||
|
|
||||||
frappe.throw(error_message)
|
frappe.throw(error_message)
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ def validate_return(doc):
|
|||||||
if doc.return_against:
|
if doc.return_against:
|
||||||
validate_return_against(doc)
|
validate_return_against(doc)
|
||||||
|
|
||||||
if doc.doctype in ("Sales Invoice", "Purchase Invoice") and not doc.update_stock:
|
|
||||||
return
|
|
||||||
|
|
||||||
validate_returned_items(doc)
|
validate_returned_items(doc)
|
||||||
|
|
||||||
|
|
||||||
@@ -118,7 +115,7 @@ def validate_returned_items(doc):
|
|||||||
elif doc.doctype == "Delivery Note":
|
elif doc.doctype == "Delivery Note":
|
||||||
key = (d.item_code, d.get("dn_detail"))
|
key = (d.item_code, d.get("dn_detail"))
|
||||||
|
|
||||||
if d.item_code and (flt(d.qty) < 0 or flt(d.get("received_qty")) < 0):
|
if d.item_code and (flt(d.qty) <= 0 or flt(d.get("received_qty")) <= 0):
|
||||||
if key not in valid_items:
|
if key not in valid_items:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("Row # {0}: Returned Item {1} does not exist in {2} {3}").format(
|
_("Row # {0}: Returned Item {1} does not exist in {2} {3}").format(
|
||||||
@@ -160,6 +157,9 @@ def validate_returned_items(doc):
|
|||||||
|
|
||||||
def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
|
def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
|
||||||
fields = ["stock_qty"]
|
fields = ["stock_qty"]
|
||||||
|
if (doc.doctype == "Purchase Invoice" or doc.doctype == "Sales Invoice") and not doc.update_stock:
|
||||||
|
fields = ["qty"]
|
||||||
|
|
||||||
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]:
|
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]:
|
||||||
if not args.get("return_qty_from_rejected_warehouse"):
|
if not args.get("return_qty_from_rejected_warehouse"):
|
||||||
fields.extend(["received_qty", "rejected_qty"])
|
fields.extend(["received_qty", "rejected_qty"])
|
||||||
@@ -169,13 +169,16 @@ def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
|
|||||||
already_returned_data = already_returned_items.get(key) or {}
|
already_returned_data = already_returned_items.get(key) or {}
|
||||||
|
|
||||||
company_currency = erpnext.get_company_currency(doc.company)
|
company_currency = erpnext.get_company_currency(doc.company)
|
||||||
stock_qty_precision = get_field_precision(
|
field_precision = get_field_precision(
|
||||||
frappe.get_meta(doc.doctype + " Item").get_field("stock_qty"), company_currency
|
frappe.get_meta(doc.doctype + " Item").get_field(
|
||||||
|
"stock_qty" if doc.get("update_stock", "") else "qty"
|
||||||
|
),
|
||||||
|
company_currency,
|
||||||
)
|
)
|
||||||
|
|
||||||
for column in fields:
|
for column in fields:
|
||||||
returned_qty = (
|
returned_qty = (
|
||||||
flt(already_returned_data.get(column, 0), stock_qty_precision)
|
flt(already_returned_data.get(column, 0), field_precision)
|
||||||
if len(already_returned_data) > 0
|
if len(already_returned_data) > 0
|
||||||
else 0
|
else 0
|
||||||
)
|
)
|
||||||
@@ -190,17 +193,17 @@ def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
|
|||||||
reference_qty = ref.get(column) * ref.get("conversion_factor", 1.0)
|
reference_qty = ref.get(column) * ref.get("conversion_factor", 1.0)
|
||||||
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
|
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
|
||||||
|
|
||||||
max_returnable_qty = flt(flt(reference_qty, stock_qty_precision) - returned_qty, stock_qty_precision)
|
max_returnable_qty = flt(flt(reference_qty, field_precision) - returned_qty, field_precision)
|
||||||
label = column.replace("_", " ").title()
|
label = column.replace("_", " ").title()
|
||||||
|
|
||||||
if reference_qty:
|
if reference_qty:
|
||||||
if flt(args.get(column)) > 0:
|
if flt(args.get(column)) > 0:
|
||||||
frappe.throw(_("{0} must be negative in return document").format(label))
|
frappe.throw(_("{0} must be negative in return document").format(label))
|
||||||
elif returned_qty >= reference_qty and args.get(column):
|
elif returned_qty >= reference_qty and args.get(column) >= 0:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Item {0} has already been returned").format(args.item_code), StockOverReturnError
|
_("Item {0} has already been returned").format(args.item_code), StockOverReturnError
|
||||||
)
|
)
|
||||||
elif abs(flt(current_stock_qty, stock_qty_precision)) > max_returnable_qty:
|
elif abs(flt(current_stock_qty, field_precision)) > max_returnable_qty:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row # {0}: Cannot return more than {1} for Item {2}").format(
|
_("Row # {0}: Cannot return more than {1} for Item {2}").format(
|
||||||
args.idx, max_returnable_qty, args.item_code
|
args.idx, max_returnable_qty, args.item_code
|
||||||
|
|||||||
@@ -220,7 +220,9 @@ class StatusUpdater(Document):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
if self.doctype not in status_map:
|
if self.doctype not in status_map:
|
||||||
return {"status": self.status}
|
return {
|
||||||
|
"status": self.get("status")
|
||||||
|
} # sometimes status field is not present on certain DocTypes such as Stock Entry
|
||||||
|
|
||||||
sl = status_map[self.doctype][:]
|
sl = status_map[self.doctype][:]
|
||||||
sl.reverse()
|
sl.reverse()
|
||||||
@@ -562,7 +564,8 @@ class StatusUpdater(Document):
|
|||||||
target = frappe.get_doc(args["target_parent_dt"], args["name"])
|
target = frappe.get_doc(args["target_parent_dt"], args["name"])
|
||||||
target.update(update_data) # status calculus might depend on it
|
target.update(update_data) # status calculus might depend on it
|
||||||
status = target.get_status()
|
status = target.get_status()
|
||||||
update_data.update(status)
|
if status.get("status"):
|
||||||
|
update_data.update(status)
|
||||||
target.db_set(update_data, update_modified=update_modified, notify=True)
|
target.db_set(update_data, update_modified=update_modified, notify=True)
|
||||||
|
|
||||||
def _update_modified(self, args, update_modified):
|
def _update_modified(self, args, update_modified):
|
||||||
|
|||||||
@@ -2179,3 +2179,59 @@ class TestAccountsController(IntegrationTestCase):
|
|||||||
si_1 = create_sales_invoice(do_not_submit=True)
|
si_1 = create_sales_invoice(do_not_submit=True)
|
||||||
si_1.items[0].project = project.name
|
si_1.items[0].project = project.name
|
||||||
self.assertRaises(frappe.ValidationError, si_1.save)
|
self.assertRaises(frappe.ValidationError, si_1.save)
|
||||||
|
|
||||||
|
def test_party_billing_and_shipping_address(self):
|
||||||
|
from erpnext.crm.doctype.prospect.test_prospect import make_address
|
||||||
|
|
||||||
|
customer_billing = make_address(address_title="Customer")
|
||||||
|
customer_billing.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
|
||||||
|
customer_billing.save()
|
||||||
|
supplier_billing = make_address(address_title="Supplier", address_line1="2", city="Ahmedabad")
|
||||||
|
supplier_billing.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
|
||||||
|
supplier_billing.save()
|
||||||
|
|
||||||
|
customer_shipping = make_address(
|
||||||
|
address_title="Customer", address_type="Shipping", address_line1="10"
|
||||||
|
)
|
||||||
|
customer_shipping.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
|
||||||
|
customer_shipping.save()
|
||||||
|
supplier_shipping = make_address(
|
||||||
|
address_title="Supplier", address_type="Shipping", address_line1="20", city="Ahmedabad"
|
||||||
|
)
|
||||||
|
supplier_shipping.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
|
||||||
|
supplier_shipping.save()
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_save=True)
|
||||||
|
si.customer_address = supplier_billing.name
|
||||||
|
self.assertRaises(frappe.ValidationError, si.save)
|
||||||
|
si.customer_address = customer_billing.name
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
si.shipping_address_name = supplier_shipping.name
|
||||||
|
self.assertRaises(frappe.ValidationError, si.save)
|
||||||
|
si.shipping_address_name = customer_shipping.name
|
||||||
|
si.reload()
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(do_not_save=True)
|
||||||
|
pi.supplier_address = customer_shipping.name
|
||||||
|
self.assertRaises(frappe.ValidationError, pi.save)
|
||||||
|
pi.supplier_address = supplier_shipping.name
|
||||||
|
pi.save()
|
||||||
|
|
||||||
|
def test_party_contact(self):
|
||||||
|
from frappe.contacts.doctype.contact.test_contact import create_contact
|
||||||
|
|
||||||
|
customer_contact = create_contact(name="Customer", salutation="Mr", save=False)
|
||||||
|
customer_contact.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
|
||||||
|
customer_contact.save()
|
||||||
|
|
||||||
|
supplier_contact = create_contact(name="Supplier", salutation="Mr", save=False)
|
||||||
|
supplier_contact.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
|
||||||
|
supplier_contact.save()
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_save=True)
|
||||||
|
si.contact_person = supplier_contact.name
|
||||||
|
self.assertRaises(frappe.ValidationError, si.save)
|
||||||
|
si.contact_person = customer_contact.name
|
||||||
|
si.save()
|
||||||
|
|||||||
1462
erpnext/locale/ar.po
1462
erpnext/locale/ar.po
File diff suppressed because it is too large
Load Diff
60544
erpnext/locale/ar_SA.po
Normal file
60544
erpnext/locale/ar_SA.po
Normal file
File diff suppressed because it is too large
Load Diff
1534
erpnext/locale/bs.po
1534
erpnext/locale/bs.po
File diff suppressed because it is too large
Load Diff
60661
erpnext/locale/bs_BA.po
Normal file
60661
erpnext/locale/bs_BA.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/de.po
1462
erpnext/locale/de.po
File diff suppressed because it is too large
Load Diff
60663
erpnext/locale/de_DE.po
Normal file
60663
erpnext/locale/de_DE.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/eo.po
1462
erpnext/locale/eo.po
File diff suppressed because it is too large
Load Diff
60544
erpnext/locale/eo_UY.po
Normal file
60544
erpnext/locale/eo_UY.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/es.po
1462
erpnext/locale/es.po
File diff suppressed because it is too large
Load Diff
60651
erpnext/locale/es_ES.po
Normal file
60651
erpnext/locale/es_ES.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/fa.po
1462
erpnext/locale/fa.po
File diff suppressed because it is too large
Load Diff
60558
erpnext/locale/fa_IR.po
Normal file
60558
erpnext/locale/fa_IR.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/fr.po
1462
erpnext/locale/fr.po
File diff suppressed because it is too large
Load Diff
60568
erpnext/locale/fr_FR.po
Normal file
60568
erpnext/locale/fr_FR.po
Normal file
File diff suppressed because it is too large
Load Diff
2096
erpnext/locale/hr.po
2096
erpnext/locale/hr.po
File diff suppressed because it is too large
Load Diff
60544
erpnext/locale/hr_HR.po
Normal file
60544
erpnext/locale/hr_HR.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/hu.po
1462
erpnext/locale/hu.po
File diff suppressed because it is too large
Load Diff
60544
erpnext/locale/hu_HU.po
Normal file
60544
erpnext/locale/hu_HU.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/pl.po
1462
erpnext/locale/pl.po
File diff suppressed because it is too large
Load Diff
60595
erpnext/locale/pl_PL.po
Normal file
60595
erpnext/locale/pl_PL.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/pt.po
1462
erpnext/locale/pt.po
File diff suppressed because it is too large
Load Diff
95583
erpnext/locale/pt_BR.po
95583
erpnext/locale/pt_BR.po
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/ru.po
1462
erpnext/locale/ru.po
File diff suppressed because it is too large
Load Diff
60544
erpnext/locale/ru_RU.po
Normal file
60544
erpnext/locale/ru_RU.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/sv.po
1462
erpnext/locale/sv.po
File diff suppressed because it is too large
Load Diff
60667
erpnext/locale/sv_SE.po
Normal file
60667
erpnext/locale/sv_SE.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/th.po
1462
erpnext/locale/th.po
File diff suppressed because it is too large
Load Diff
60544
erpnext/locale/th_TH.po
Normal file
60544
erpnext/locale/th_TH.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/tr.po
1462
erpnext/locale/tr.po
File diff suppressed because it is too large
Load Diff
60667
erpnext/locale/tr_TR.po
Normal file
60667
erpnext/locale/tr_TR.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
erpnext/locale/zh.po
1462
erpnext/locale/zh.po
File diff suppressed because it is too large
Load Diff
60648
erpnext/locale/zh_CN.po
Normal file
60648
erpnext/locale/zh_CN.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -217,7 +217,7 @@ frappe.ui.form.on("Job Card", {
|
|||||||
label: __("Completed Quantity"),
|
label: __("Completed Quantity"),
|
||||||
fieldname: "qty",
|
fieldname: "qty",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
default: frm.doc.for_quantity - frm.doc.manufactured_qty,
|
default: frm.doc.for_quantity - frm.doc.total_completed_qty,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldtype: "Datetime",
|
fieldtype: "Datetime",
|
||||||
|
|||||||
@@ -426,6 +426,7 @@ class ProductionPlan(Document):
|
|||||||
mr_item.item_code,
|
mr_item.item_code,
|
||||||
mr_item.warehouse,
|
mr_item.warehouse,
|
||||||
mr_item.description,
|
mr_item.description,
|
||||||
|
mr_item.bom_no,
|
||||||
((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor).as_("pending_qty"),
|
((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor).as_("pending_qty"),
|
||||||
)
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ class WorkOrder(Document):
|
|||||||
if self.source_warehouse:
|
if self.source_warehouse:
|
||||||
self.set_warehouses()
|
self.set_warehouses()
|
||||||
|
|
||||||
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
|
validate_uom_is_integer(self, "stock_uom", ["required_qty"])
|
||||||
|
|
||||||
self.set_required_items(reset_only_qty=len(self.get("required_items")))
|
self.set_required_items(reset_only_qty=len(self.get("required_items")))
|
||||||
self.enable_auto_reserve_stock()
|
self.enable_auto_reserve_stock()
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ def get_periodic_data(filters, entry):
|
|||||||
]:
|
]:
|
||||||
if d.status in ["Not Started", "Closed", "Stopped"]:
|
if d.status in ["Not Started", "Closed", "Stopped"]:
|
||||||
periodic_data = update_periodic_data(periodic_data, d.status, period)
|
periodic_data = update_periodic_data(periodic_data, d.status, period)
|
||||||
elif today() > getdate(d.planned_end_date):
|
elif getdate(today()) > getdate(d.planned_end_date):
|
||||||
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
|
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
|
||||||
elif today() < getdate(d.planned_end_date):
|
elif getdate(today()) < getdate(d.planned_end_date):
|
||||||
periodic_data = update_periodic_data(periodic_data, "Pending", period)
|
periodic_data = update_periodic_data(periodic_data, "Pending", period)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ erpnext.patches.v14_0.disable_add_row_in_gross_profit
|
|||||||
erpnext.patches.v14_0.update_posting_datetime
|
erpnext.patches.v14_0.update_posting_datetime
|
||||||
erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference
|
erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference
|
||||||
erpnext.patches.v15_0.recalculate_amount_difference_field
|
erpnext.patches.v15_0.recalculate_amount_difference_field
|
||||||
erpnext.patches.v15_0.rename_sla_fields
|
erpnext.patches.v15_0.rename_sla_fields #2025-03-12
|
||||||
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
|
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
|
||||||
erpnext.patches.v15_0.update_query_report
|
erpnext.patches.v15_0.update_query_report
|
||||||
erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item
|
erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item
|
||||||
|
|||||||
@@ -62,6 +62,14 @@ def execute():
|
|||||||
):
|
):
|
||||||
posting_date = period_closing_voucher[0].period_end_date
|
posting_date = period_closing_voucher[0].period_end_date
|
||||||
|
|
||||||
|
acc_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
|
||||||
|
if acc_frozen_upto and getdate(acc_frozen_upto) > getdate(posting_date):
|
||||||
|
posting_date = acc_frozen_upto
|
||||||
|
|
||||||
|
stock_frozen_upto = frappe.db.get_single_value("Stock Settings", "stock_frozen_upto")
|
||||||
|
if stock_frozen_upto and getdate(stock_frozen_upto) > getdate(posting_date):
|
||||||
|
posting_date = stock_frozen_upto
|
||||||
|
|
||||||
fiscal_year = get_fiscal_year(frappe.utils.datetime.date.today(), raise_on_missing=False)
|
fiscal_year = get_fiscal_year(frappe.utils.datetime.date.today(), raise_on_missing=False)
|
||||||
if fiscal_year and getdate(fiscal_year[1]) > getdate(posting_date):
|
if fiscal_year and getdate(fiscal_year[1]) > getdate(posting_date):
|
||||||
posting_date = fiscal_year[1]
|
posting_date = fiscal_year[1]
|
||||||
|
|||||||
@@ -4,15 +4,19 @@ from frappe.model.utils.rename_field import rename_field
|
|||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
doctypes = frappe.get_all("Service Level Agreement", pluck="document_type")
|
doctypes = frappe.get_all("Service Level Agreement", pluck="document_type", distinct=True)
|
||||||
for doctype in doctypes:
|
for doctype in doctypes:
|
||||||
if doctype == "Issue":
|
if doctype == "Issue":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if frappe.db.exists("Custom Field", {"fieldname": doctype + "-resolution_by"}):
|
if frappe.db.exists(
|
||||||
|
"Custom Field", {"name": doctype + "-resolution_by", "fieldname": "resolution_by"}
|
||||||
|
):
|
||||||
rename_fieldname(doctype + "-resolution_by", "sla_resolution_by")
|
rename_fieldname(doctype + "-resolution_by", "sla_resolution_by")
|
||||||
|
|
||||||
if frappe.db.exists("Custom Field", {"fieldname": doctype + "-resolution_date"}):
|
if frappe.db.exists(
|
||||||
|
"Custom Field", {"name": doctype + "-resolution_date", "fieldname": "resolution_date"}
|
||||||
|
):
|
||||||
rename_fieldname(doctype + "-resolution_date", "sla_resolution_date")
|
rename_fieldname(doctype + "-resolution_date", "sla_resolution_date")
|
||||||
|
|
||||||
rename_field("Issue", "resolution_by", "sla_resolution_by")
|
rename_field("Issue", "resolution_by", "sla_resolution_by")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
"autoname": "Prompt",
|
"autoname": "Prompt",
|
||||||
"creation": "2019-02-18 17:23:11.708371",
|
"creation": "2019-02-18 17:23:11.708371",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@@ -7,6 +8,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"project_type",
|
"project_type",
|
||||||
|
"disabled",
|
||||||
"tasks"
|
"tasks"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -23,13 +25,20 @@
|
|||||||
"label": "Tasks",
|
"label": "Tasks",
|
||||||
"options": "Project Template Task",
|
"options": "Project Template Task",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "disabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Disabled"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:10:21.325199",
|
"modified": "2025-03-12 14:20:57.301906",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Project Template",
|
"name": "Project Template",
|
||||||
|
"naming_rule": "Set by user",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@@ -46,6 +55,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class ProjectTemplate(Document):
|
|||||||
ProjectTemplateTask,
|
ProjectTemplateTask,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
disabled: DF.Check
|
||||||
project_type: DF.Link | None
|
project_type: DF.Link | None
|
||||||
tasks: DF.Table[ProjectTemplateTask]
|
tasks: DF.Table[ProjectTemplateTask]
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|||||||
@@ -262,12 +262,11 @@ def sales_invoice_validate(doc):
|
|||||||
|
|
||||||
doc.company_tax_id = frappe.get_cached_value("Company", doc.company, "tax_id")
|
doc.company_tax_id = frappe.get_cached_value("Company", doc.company, "tax_id")
|
||||||
doc.company_fiscal_code = frappe.get_cached_value("Company", doc.company, "fiscal_code")
|
doc.company_fiscal_code = frappe.get_cached_value("Company", doc.company, "fiscal_code")
|
||||||
if not doc.company_tax_id and not doc.company_fiscal_code:
|
if not doc.company_tax_id or not doc.company_fiscal_code:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Please set either the Tax ID or Fiscal Code on Company '%s'" % doc.company),
|
_("Please set both the Tax ID and Fiscal Code on Company {0}").format(doc.company),
|
||||||
title=_("E-Invoicing Information Missing"),
|
title=_("E-Invoicing Information Missing"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate customer details
|
# Validate customer details
|
||||||
customer = frappe.get_doc("Customer", doc.customer)
|
customer = frappe.get_doc("Customer", doc.customer)
|
||||||
|
|
||||||
|
|||||||
@@ -892,6 +892,7 @@ def make_material_request(source_name, target_doc=None):
|
|||||||
"name": "sales_order_item",
|
"name": "sales_order_item",
|
||||||
"parent": "sales_order",
|
"parent": "sales_order",
|
||||||
"delivery_date": "required_by",
|
"delivery_date": "required_by",
|
||||||
|
"bom_no": "bom_no",
|
||||||
},
|
},
|
||||||
"condition": lambda item: not frappe.db.exists(
|
"condition": lambda item: not frappe.db.exists(
|
||||||
"Product Bundle", {"name": item.item_code, "disabled": 0}
|
"Product Bundle", {"name": item.item_code, "disabled": 0}
|
||||||
|
|||||||
@@ -449,6 +449,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
init_order_summary() {
|
init_order_summary() {
|
||||||
this.order_summary = new erpnext.PointOfSale.PastOrderSummary({
|
this.order_summary = new erpnext.PointOfSale.PastOrderSummary({
|
||||||
wrapper: this.$components_wrapper,
|
wrapper: this.$components_wrapper,
|
||||||
|
settings: this.settings,
|
||||||
events: {
|
events: {
|
||||||
get_frm: () => this.frm,
|
get_frm: () => this.frm,
|
||||||
|
|
||||||
@@ -489,7 +490,6 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pos_profile: this.pos_profile,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
erpnext.PointOfSale.PastOrderSummary = class {
|
erpnext.PointOfSale.PastOrderSummary = class {
|
||||||
constructor({ wrapper, events, pos_profile }) {
|
constructor({ wrapper, settings, events }) {
|
||||||
this.wrapper = wrapper;
|
this.wrapper = wrapper;
|
||||||
this.events = events;
|
this.events = events;
|
||||||
this.pos_profile = pos_profile;
|
this.print_receipt_on_order_complete = settings.print_receipt_on_order_complete;
|
||||||
|
|
||||||
this.init_component();
|
this.init_component();
|
||||||
}
|
}
|
||||||
@@ -391,8 +391,8 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
|
|
||||||
this.add_summary_btns(condition_btns_map);
|
this.add_summary_btns(condition_btns_map);
|
||||||
|
|
||||||
if (after_submission) {
|
if (after_submission && this.print_receipt_on_order_complete) {
|
||||||
this.print_receipt_on_order_complete();
|
this.print_receipt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,18 +461,6 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
show ? this.$component.css("display", "flex") : this.$component.css("display", "none");
|
show ? this.$component.css("display", "flex") : this.$component.css("display", "none");
|
||||||
}
|
}
|
||||||
|
|
||||||
async print_receipt_on_order_complete() {
|
|
||||||
const res = await frappe.db.get_value(
|
|
||||||
"POS Profile",
|
|
||||||
this.pos_profile,
|
|
||||||
"print_receipt_on_order_complete"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res.message.print_receipt_on_order_complete) {
|
|
||||||
this.print_receipt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async is_pos_invoice_returnable(invoice) {
|
async is_pos_invoice_returnable(invoice) {
|
||||||
const r = await frappe.call({
|
const r = await frappe.call({
|
||||||
method: "erpnext.controllers.sales_and_purchase_return.is_pos_invoice_returnable",
|
method: "erpnext.controllers.sales_and_purchase_return.is_pos_invoice_returnable",
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ class Employee(NestedSet):
|
|||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.update_nsm_model()
|
self.update_nsm_model()
|
||||||
|
frappe.clear_cache()
|
||||||
if self.user_id:
|
if self.user_id:
|
||||||
self.update_user()
|
self.update_user()
|
||||||
self.update_user_permissions()
|
self.update_user_permissions()
|
||||||
|
|||||||
@@ -937,17 +937,19 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-02-05 14:27:32.322181",
|
"modified": "2025-03-07 12:33:40.868499",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note Item",
|
"name": "Delivery Note Item",
|
||||||
"naming_rule": "Random",
|
"naming_rule": "Random",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
|
|||||||
@@ -884,7 +884,7 @@ def create_pick_list(source_name, target_doc=None):
|
|||||||
},
|
},
|
||||||
"Material Request Item": {
|
"Material Request Item": {
|
||||||
"doctype": "Pick List Item",
|
"doctype": "Pick List Item",
|
||||||
"field_map": {"name": "material_request_item", "qty": "stock_qty"},
|
"field_map": {"name": "material_request_item", "stock_qty": "stock_qty"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
target_doc,
|
target_doc,
|
||||||
|
|||||||
@@ -1189,7 +1189,6 @@ def create_delivery_note(source_name, target_doc=None):
|
|||||||
if not all(item.sales_order for item in pick_list.locations):
|
if not all(item.sales_order for item in pick_list.locations):
|
||||||
delivery_note = create_dn_wo_so(pick_list)
|
delivery_note = create_dn_wo_so(pick_list)
|
||||||
|
|
||||||
frappe.msgprint(_("Delivery Note(s) created for the Pick List"))
|
|
||||||
return delivery_note
|
return delivery_note
|
||||||
|
|
||||||
|
|
||||||
@@ -1206,7 +1205,6 @@ def create_dn_wo_so(pick_list):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
map_pl_locations(pick_list, item_table_mapper_without_so, delivery_note)
|
map_pl_locations(pick_list, item_table_mapper_without_so, delivery_note)
|
||||||
delivery_note.insert(ignore_mandatory=True)
|
|
||||||
|
|
||||||
return delivery_note
|
return delivery_note
|
||||||
|
|
||||||
@@ -1234,10 +1232,7 @@ def create_dn_with_so(sales_dict, pick_list):
|
|||||||
# map all items of all sales orders of that customer
|
# map all items of all sales orders of that customer
|
||||||
for so in sales_dict[customer]:
|
for so in sales_dict[customer]:
|
||||||
map_pl_locations(pick_list, item_table_mapper, delivery_note, so)
|
map_pl_locations(pick_list, item_table_mapper, delivery_note, so)
|
||||||
delivery_note.flags.ignore_mandatory = True
|
|
||||||
delivery_note.insert()
|
|
||||||
update_packed_item_details(pick_list, delivery_note)
|
update_packed_item_details(pick_list, delivery_note)
|
||||||
delivery_note.save()
|
|
||||||
|
|
||||||
return delivery_note
|
return delivery_note
|
||||||
|
|
||||||
|
|||||||
@@ -195,6 +195,16 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend
|
|||||||
super.setup(doc);
|
super.setup(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onload() {
|
||||||
|
this.frm.set_query("supplier", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_transporter: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
var me = this;
|
var me = this;
|
||||||
super.refresh();
|
super.refresh();
|
||||||
|
|||||||
@@ -1018,7 +1018,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "serial_no",
|
"fieldname": "serial_no",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"label": "Serial No"
|
"label": "Serial No",
|
||||||
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "rejected_serial_no",
|
"fieldname": "rejected_serial_no",
|
||||||
@@ -1147,7 +1148,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-02-17 13:15:36.692202",
|
"modified": "2025-03-07 10:25:15.554985",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
@@ -1155,6 +1156,7 @@
|
|||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
|
|||||||
@@ -2112,6 +2112,9 @@ def get_auto_batch_nos(kwargs):
|
|||||||
picked_batches,
|
picked_batches,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if available_batches and kwargs.get("posting_date"):
|
||||||
|
filter_zero_near_batches(available_batches, kwargs)
|
||||||
|
|
||||||
if not kwargs.consider_negative_batches:
|
if not kwargs.consider_negative_batches:
|
||||||
available_batches = list(filter(lambda x: x.qty > 0, available_batches))
|
available_batches = list(filter(lambda x: x.qty > 0, available_batches))
|
||||||
|
|
||||||
@@ -2121,6 +2124,26 @@ def get_auto_batch_nos(kwargs):
|
|||||||
return get_qty_based_available_batches(available_batches, qty)
|
return get_qty_based_available_batches(available_batches, qty)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_zero_near_batches(available_batches, kwargs):
|
||||||
|
kwargs.batch_no = [d.batch_no for d in available_batches]
|
||||||
|
|
||||||
|
del kwargs["posting_date"]
|
||||||
|
del kwargs["posting_time"]
|
||||||
|
|
||||||
|
available_batches_in_future = get_available_batches(kwargs)
|
||||||
|
for batch in available_batches:
|
||||||
|
if batch.qty <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for future_batch in available_batches_in_future:
|
||||||
|
if (
|
||||||
|
batch.batch_no == future_batch.batch_no
|
||||||
|
and batch.warehouse == future_batch.warehouse
|
||||||
|
and future_batch.qty <= 0
|
||||||
|
):
|
||||||
|
batch.qty = 0
|
||||||
|
|
||||||
|
|
||||||
def get_qty_based_available_batches(available_batches, qty):
|
def get_qty_based_available_batches(available_batches, qty):
|
||||||
batches = []
|
batches = []
|
||||||
for batch in available_batches:
|
for batch in available_batches:
|
||||||
|
|||||||
@@ -964,6 +964,9 @@ class StockReconciliation(StockController):
|
|||||||
if voucher_detail_no != row.name:
|
if voucher_detail_no != row.name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if row.current_qty < 0:
|
||||||
|
return
|
||||||
|
|
||||||
val_rate = 0.0
|
val_rate = 0.0
|
||||||
current_qty = 0.0
|
current_qty = 0.0
|
||||||
if row.current_serial_and_batch_bundle:
|
if row.current_serial_and_batch_bundle:
|
||||||
|
|||||||
@@ -110,7 +110,8 @@
|
|||||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||||
"fieldname": "serial_no",
|
"fieldname": "serial_no",
|
||||||
"fieldtype": "Long Text",
|
"fieldtype": "Long Text",
|
||||||
"label": "Serial No"
|
"label": "Serial No",
|
||||||
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_11",
|
"fieldname": "column_break_11",
|
||||||
@@ -253,15 +254,17 @@
|
|||||||
"label": "Reconcile All Serial Nos / Batches"
|
"label": "Reconcile All Serial Nos / Batches"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-05-30 23:20:00.947243",
|
"modified": "2025-03-07 10:26:25.856337",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Reconciliation Item",
|
"name": "Stock Reconciliation Item",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
|
|||||||
@@ -299,9 +299,12 @@ class StockBalanceReport:
|
|||||||
elif entry.posting_date >= self.from_date and entry.posting_date <= self.to_date:
|
elif entry.posting_date >= self.from_date and entry.posting_date <= self.to_date:
|
||||||
if flt(qty_diff, self.float_precision) >= 0:
|
if flt(qty_diff, self.float_precision) >= 0:
|
||||||
qty_dict.in_qty += qty_diff
|
qty_dict.in_qty += qty_diff
|
||||||
qty_dict.in_val += value_diff
|
|
||||||
else:
|
else:
|
||||||
qty_dict.out_qty += abs(qty_diff)
|
qty_dict.out_qty += abs(qty_diff)
|
||||||
|
|
||||||
|
if flt(value_diff, self.float_precision) >= 0:
|
||||||
|
qty_dict.in_val += value_diff
|
||||||
|
else:
|
||||||
qty_dict.out_val += abs(value_diff)
|
qty_dict.out_val += abs(value_diff)
|
||||||
|
|
||||||
qty_dict.val_rate = entry.valuation_rate
|
qty_dict.val_rate = entry.valuation_rate
|
||||||
|
|||||||
@@ -318,16 +318,29 @@ class TransactionBase(StatusUpdater):
|
|||||||
"warehouse": item_obj.from_warehouse
|
"warehouse": item_obj.from_warehouse
|
||||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]
|
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]
|
||||||
else item_obj.warehouse,
|
else item_obj.warehouse,
|
||||||
"posting_date": self.posting_date,
|
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"qty": item_obj.qty * item_obj.conversion_factor,
|
"qty": item_obj.qty * item_obj.conversion_factor,
|
||||||
"serial_no": item_obj.serial_no,
|
|
||||||
"batch_no": item_obj.batch_no,
|
|
||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"allow_zero_valuation_rate": item_obj.allow_zero_valuation_rate,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.doctype in ["Purchase Order", "Sales Order"]:
|
||||||
|
args.update(
|
||||||
|
{
|
||||||
|
"posting_date": self.transaction_date,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
args.update(
|
||||||
|
{
|
||||||
|
"posting_date": self.posting_date,
|
||||||
|
"posting_time": self.posting_time,
|
||||||
|
"serial_no": item_obj.serial_no,
|
||||||
|
"batch_no": item_obj.batch_no,
|
||||||
|
"allow_zero_valuation_rate": item_obj.allow_zero_valuation_rate,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
rate = get_incoming_rate(args=args)
|
rate = get_incoming_rate(args=args)
|
||||||
item_obj.rate = rate * item_obj.conversion_factor
|
item_obj.rate = rate * item_obj.conversion_factor
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user