From f94d607288b4e62b85dc8397c67d26eb6a56e092 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 1 Apr 2022 16:28:34 +0530 Subject: [PATCH 01/98] feat: New module "Subcontracting" --- erpnext/modules.txt | 1 + erpnext/subcontracting/__init__.py | 0 2 files changed, 1 insertion(+) create mode 100644 erpnext/subcontracting/__init__.py diff --git a/erpnext/modules.txt b/erpnext/modules.txt index c6b3159e0f..e194b4d7c4 100644 --- a/erpnext/modules.txt +++ b/erpnext/modules.txt @@ -22,3 +22,4 @@ Payroll Telephony Bulk Transaction E-commerce +Subcontracting \ No newline at end of file diff --git a/erpnext/subcontracting/__init__.py b/erpnext/subcontracting/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 08433441190c7bfa257b0fb8d99ab131018244b1 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Tue, 17 May 2022 16:21:42 +0300 Subject: [PATCH 02/98] Update ru.csv --- erpnext/translations/ru.csv | 166 ++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 6447546d22..853c571dad 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -1,18 +1,18 @@ -"""Customer Provided Item"" cannot be Purchase Item also","«Товар, предоставленный клиентом» также не может быть предметом покупки", -"""Customer Provided Item"" cannot have Valuation Rate",«Предоставленный клиентом товар» не может иметь оценку, -"""Is Fixed Asset"" cannot be unchecked, as Asset record exists against the item","Нельзя отменить выбор ""Является основным средством"", поскольку по данному пункту имеется запись по активам", -'Based On' and 'Group By' can not be same,"""На основании"" и ""Группировка по"" не могут быть одинаковыми", -'Days Since Last Order' must be greater than or equal to zero,"""Дней с момента последнего заказа"" должно быть больше или равно 0", -'Entries' cannot be empty,"""Записи"" не могут быть пустыми", -'From Date' is required,"Поле ""С даты"" является обязательным для заполнения", -'From Date' must be after 'To Date',"Поле ""С даты"" должно быть после ""До даты""", -'Has Serial No' can not be 'Yes' for non-stock item,«Имеет серийный номер» не может быть «Да» для нескладируемого продукта, -'Opening',«Открывается», -'To Case No.' cannot be less than 'From Case No.',"«До дела №» не может быть меньше, чем «От дела №»", -'To Date' is required,"Поле ""До Даты"" является обязательным для заполнения", -'Total',«Итого», -'Update Stock' can not be checked because items are not delivered via {0},"Нельзя выбрать «Обновить запасы», так как продукты не поставляются через {0}", -'Update Stock' cannot be checked for fixed asset sale,"""Обновить запасы"" нельзя выбрать при продаже основных средств", +"""Customer Provided Item"" cannot be Purchase Item also","""Товар, предоставленный клиентом"" не может быть предметом покупки", +"""Customer Provided Item"" cannot have Valuation Rate","""Предоставленный клиентом товар"" не может иметь оценку", +"""Is Fixed Asset"" cannot be unchecked, as Asset record exists against the item","Нельзя убрать отметку ""Является основным средством"", поскольку по данному пункту имеется запись по активам", +'Based On' and 'Group By' can not be same,'На основании' и 'Группировка по' не могут быть одинаковыми, +'Days Since Last Order' must be greater than or equal to zero,'Дней с момента последнего заказа' должно быть больше или равно 0, +'Entries' cannot be empty,'Записи' не могут быть пустыми, +'From Date' is required,Поле 'С даты' является обязательным для заполнения, +'From Date' must be after 'To Date',Значение 'С даты' должно быть после 'До даты', +'Has Serial No' can not be 'Yes' for non-stock item,'Имеет серийный номер' не может быть 'Да' для нескладируемого продукта, +'Opening','Открытие', +'To Case No.' cannot be less than 'From Case No.',"'До дела №' не может быть меньше, чем 'От дела №'", +'To Date' is required,Поле 'До Даты' является обязательным для заполнения, +'Total','Итого', +'Update Stock' can not be checked because items are not delivered via {0},"Нельзя выбрать 'Обновить запасы', так как продукты не поставляются через {0}", +'Update Stock' cannot be checked for fixed asset sale,"'Обновить запасы' нельзя выбрать при продаже основных средств", ) for {0},) для {0}, 1 exact match.,1 точное совпадение., 90-Above,90-Над, @@ -897,7 +897,7 @@ ERPNext Settings,Настройки ERPNext, Earliest,Самый ранний, Earnest Money,Задаток, Earning,Зарабатывание, -Edit,Ред., +Edit,Редактировать, Edit Publishing Details,Редактировать информацию о публикации, "Edit in full page for more options like assets, serial nos, batches etc.","Редактируйте на полной странице дополнительные параметры, такие как активы, серийные номера, партии и т. Д.", Education,образование, @@ -1400,7 +1400,7 @@ Job card {0} created,Карта работы {0} создана, Jobs,Работы, Join,Присоединиться, Journal Entries {0} are un-linked,Записи в журнале {0} не-связаны, -Journal Entry,Запись в дневнике, +Journal Entry,Запись в журнале, Journal Entry {0} does not have account {1} or already matched against other voucher,Запись в журнале {0} не имеете учет {1} или уже сравнивается с другой ваучер, Kanban Board,Канбан-доска, Key Reports,Ключевые отчеты, @@ -1577,10 +1577,10 @@ Meeting,Встреча, Member Activity,Активность участника, Member ID,ID пользователя, Member Name,Имя участника, -Member information.,Информация о членах., +Member information.,Информация об участниках., Membership,Членство, Membership Details,Сведения о членстве, -Membership ID,Идентификатор членства, +Membership ID,Идентификатор участника, Membership Type,Тип членства, Memebership Details,Меморандум, Memebership Type Details,Информация о типе памяти, @@ -1939,7 +1939,7 @@ Pharmaceutical,Фармацевтический, Pharmaceuticals,Фармацевтика, Physician,Врач, Piecework,Сдельная работа, -Pincode,Pincode, +Pincode,PIN код, Place Of Supply (State/UT),Место поставки (штат / UT), Place Order,Разместить заказ, Plan Name,Название плана, @@ -1953,7 +1953,7 @@ Please add a Temporary Opening account in Chart of Accounts,"Пожалуйст Please add the account to root level Company - ,"Пожалуйста, добавьте счет на корневой уровень компании -", Please add the remaining benefits {0} to any of the existing component,Добавьте оставшиеся преимущества {0} к любому из существующих компонентов, Please check Multi Currency option to allow accounts with other currency,"Пожалуйста, проверьте мультивалютный вариант, позволяющий счета другой валюте", -Please click on 'Generate Schedule',"Пожалуйста, нажмите на кнопку ""Создать расписание""", +Please click on 'Generate Schedule',"Пожалуйста, нажмите на кнопку 'Создать расписание'", Please click on 'Generate Schedule' to fetch Serial No added for Item {0},"Пожалуйста, нажмите на кнопку ""Создать расписание"", чтобы принести Серийный номер добавлен для Пункт {0}", Please click on 'Generate Schedule' to get schedule,"Пожалуйста, нажмите на кнопку ""Создать расписание"", чтобы получить график", Please confirm once you have completed your training,"Пожалуйста, подтвердите, как только вы закончили обучение", @@ -1963,7 +1963,7 @@ Please enable Applicable on Booking Actual Expenses,"Пожалуйста, вк Please enable Applicable on Purchase Order and Applicable on Booking Actual Expenses,"Пожалуйста, включите Применимо по заказу на поставку и применимо при бронировании Фактические расходы", Please enable default incoming account before creating Daily Work Summary Group,"Включите учетную запись по умолчанию, прежде чем создавать сводную группу ежедневных работ", Please enable pop-ups,"Пожалуйста, включите всплывающие окна", -Please enter 'Is Subcontracted' as Yes or No,"Пожалуйста, введите 'Является субподряду "", как Да или Нет", +Please enter 'Is Subcontracted' as Yes or No,"Пожалуйста, введите 'Является субподрядом', как Да или Нет", Please enter API Consumer Key,Введите API-адрес потребителя, Please enter API Consumer Secret,"Пожалуйста, введите секретный раздел API", Please enter Account for Change Amount,"Пожалуйста, введите счет для изменения высоты", @@ -2230,7 +2230,7 @@ Purchase Manager,Менеджер поставок, Purchase Master Manager,Руководитель поставок, Purchase Order,Заказ на покупку, Purchase Order Amount,Сумма заказа на покупку, -Purchase Order Amount(Company Currency),Сумма заказа на покупку (валюта компании), +Purchase Order Amount(Company Currency),Сумма заказа на покупку (в валюте компании), Purchase Order Date,Дата заказа на покупку, Purchase Order Items not received on time,Элементы заказа на поставку не принимаются вовремя, Purchase Order number required for Item {0},Число Заказ требуется для продукта {0}, @@ -3035,7 +3035,7 @@ To Date cannot be before From Date,На сегодняшний день не м To Date cannot be less than From Date,"Дата не может быть меньше, чем с даты", To Date must be greater than From Date,"До даты должно быть больше, чем с даты", "To Date should be within the Fiscal Year. Assuming To Date = {0}","Дата должна быть в пределах финансового года. Предположим, до даты = {0}", -To Datetime,Для DateTime, +To Datetime,Ко времени, To Deliver,Для доставки, To Deliver and Bill,Для доставки и оплаты, To Fiscal Year,К финансовому году, @@ -3537,7 +3537,7 @@ Quality Feedback Template,Шаблон обратной связи по каче Rules for applying different promotional schemes.,Правила применения разных рекламных схем., Shift,Сдвиг, Show {0},Показать {0}, -"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Специальные символы, кроме "-", "#", ".", "/", "{" И "}", не допускаются в именных сериях", +"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Специальные символы, кроме ""-"", ""#"", ""."", ""/"", ""{"" и ""}"", не допускаются в серийных номерах", Target Details,Детали цели, {0} already has a Parent Procedure {1}.,{0} уже имеет родительскую процедуру {1}., API,API, @@ -3681,8 +3681,8 @@ Create New Contact,Создать новый контакт, Create New Lead,Создать новый лид, Create Pick List,Создать список выбора, Create Quality Inspection for Item {0},Создать проверку качества для позиции {0}, -Creating Accounts...,Создание аккаунтов ..., -Creating bank entries...,Создание банковских записей ..., +Creating Accounts...,Создание счетов..., +Creating bank entries...,Создание банковских записей..., Credit limit is already defined for the Company {0},Кредитный лимит уже определен для Компании {0}, Ctrl + Enter to submit,Ctrl + Enter для отправки, Ctrl+Enter to submit,Ctrl + Enter для отправки, @@ -3694,7 +3694,7 @@ Daily,Ежедневно, Date,Дата, Date Range,Диапазон дат, Date of Birth cannot be greater than Joining Date.,Дата рождения не может быть больше даты присоединения., -Dear,Уважаемый (ая), +Dear,Уважаемый(ая), Default,По умолчанию, Define coupon codes.,Определить коды купонов., Delayed Days,Задержанные дни, @@ -3836,7 +3836,7 @@ Make Journal Entry,Сделать запись в журнале, Make Purchase Invoice,Сделать счет на покупку, Manufactured,Изготовлено, Mark Work From Home,Пометить работу из дома, -Master,Магистр, +Master,Мастер, Max strength cannot be less than zero.,Максимальная сила не может быть меньше нуля., Maximum attempts for this quiz reached!,Максимальное количество попыток для этого теста достигнуто!, Message,Сообщение, @@ -3899,7 +3899,7 @@ Penalty Amount,Сумма штрафа, Pending,В ожидании, Performance,Производительность, Period based On,Период на основе, -Perpetual inventory required for the company {0} to view this report.,"Постоянная инвентаризация требуется для компании {0}, чтобы просмотреть этот отчет.", +Perpetual inventory required for the company {0} to view this report.,"Чтобы посмотреть этот отчет, требуется постоянная инвентаризация для комнаии {0}", Phone,Телефон, Pick List,Список выбора, Plaid authentication error,Ошибка аутентификации пледа, @@ -4224,7 +4224,7 @@ To date cannot be before From date,На сегодняшний день не м Write Off,Списать, {0} Created,{0} Создано, Email Id,Email ID, -No,№, +No,Нет, Reference Doctype,Ссылка DocType, User Id,ID пользователя, Yes,Да, @@ -4660,7 +4660,7 @@ POS-CLO-,POS-CLO-, Custody,Опека, Net Amount,Чистая сумма, Cashier Closing Payments,Кассовые платежи, -Chart of Accounts Importer,План счетов импортера, +Chart of Accounts Importer,Импорт плана счетов, Import Chart of Accounts from a csv file,Импортировать план счетов из CSV-файла, Attach custom Chart of Accounts file,Прикрепить пользовательский файл плана счетов, Chart Preview,Предварительный просмотр диаграммы, @@ -4668,12 +4668,12 @@ Chart Tree,Дерево Диаграммы, Cheque Print Template,Чеками печати шаблона, Has Print Format,Имеет формат печати, Primary Settings,Основные настройки, -Cheque Size,Cheque Размер, +Cheque Size,Размер чека, Regular,Обычный, Starting position from top edge,Исходное положение от верхнего края, -Cheque Width,Cheque Ширина, -Cheque Height,Cheque Высота, -Scanned Cheque,Сканированные чеками, +Cheque Width,Ширина чека, +Cheque Height,Высота чека, +Scanned Cheque,Отсканированный чек, Is Account Payable,Является ли кредиторская задолженность, Distance from top edge,Расстояние от верхнего края, Distance from left edge,Расстояние от левого края, @@ -4693,9 +4693,9 @@ lft,лев, rgt,прав, Coupon Code,Код купона, Coupon Name,Название купона, -"e.g. ""Summer Holiday 2019 Offer 20""","например, "Летние каникулы 2019 Предложение 20"", +"e.g. ""Summer Holiday 2019 Offer 20""","например, ""Летние каникулы 2019 Предложение 20""", Coupon Type,Тип купона, -Promotional,рекламный, +Promotional,Рекламный, Gift Card,Подарочная карта, unique e.g. SAVE20 To be used to get discount,"уникальный, например, SAVE20 для получения скидки", Validity and Usage,Срок действия и использование, @@ -4760,7 +4760,7 @@ Excise Entry,Акцизный запись, Write Off Entry,Списание запись, Opening Entry,Начальная запись, ACC-JV-.YYYY.-,ACC-JV-.YYYY.-, -Accounting Entries,Бухгалтерские Проводки, +Accounting Entries,Бухгалтерские проводки, Total Debit,Общий дебет, Total Credit,Общий кредит, Difference (Dr - Cr),Разница (Деб - Кред), @@ -4840,14 +4840,14 @@ Account Paid From,Счет Оплачено из, Account Paid To,Счет оплачены до, Paid Amount (Company Currency),Оплаченная сумма (в валюте компании), Received Amount,Полученная сумма, -Received Amount (Company Currency),Полученная сумма (валюта компании), +Received Amount (Company Currency),Полученная сумма (в валюте компании), Get Outstanding Invoice,Получить выдающийся счет, Payment References,Ссылки оплаты, Writeoff,Списать, -Total Allocated Amount,Общая сумма Обозначенная, -Total Allocated Amount (Company Currency),Общая Выделенная сумма (валюта компании), +Total Allocated Amount,Общая выделенная сумма, +Total Allocated Amount (Company Currency),Общая выделенная сумма (в валюте компании), Set Exchange Gain / Loss,Установить Курсовая прибыль / убыток, -Difference Amount (Company Currency),Разница Сумма (валюта компании), +Difference Amount (Company Currency),Разница (в валюте компании), Write Off Difference Amount,Списание разница в, Deductions or Loss,Отчисления или убыток, Payment Deductions or Loss,Отчисления оплаты или убыток, @@ -5002,35 +5002,35 @@ Raw Materials Supplied,Поставка сырья, Supplier Warehouse,Склад поставщика, Pricing Rules,Правила ценообразования, Supplied Items,Поставляемые продукты, -Total (Company Currency),Всего (валюта компании), -Net Total (Company Currency),Чистая Всего (валюта компании), +Total (Company Currency),Всего (в валюте компании), +Net Total (Company Currency),Чистая Всего (в валюте компании), Total Net Weight,Общий вес нетто, Shipping Rule,Правило доставки, Purchase Taxes and Charges Template,Купить налоги и сборы шаблон, Purchase Taxes and Charges,Покупка Налоги и сборы, Tax Breakup,Распределение налогов, Taxes and Charges Calculation,Налоги и сборы Расчет, -Taxes and Charges Added (Company Currency),Налоги и сборы Добавил (валюта компании), -Taxes and Charges Deducted (Company Currency),"Налоги, которые вычитаются (валюта компании)", -Total Taxes and Charges (Company Currency),Всего Налоги и сборы (валюта компании), +Taxes and Charges Added (Company Currency),Добавленные налоги и сборы (в валюте компании), +Taxes and Charges Deducted (Company Currency),"Налоги, которые вычитаются (в валюте компании)", +Total Taxes and Charges (Company Currency),Всего налогов и сборов (в валюте компании), Taxes and Charges Added,Налоги и сборы добавлены, Taxes and Charges Deducted,"Налоги и сборы, вычитаемые", Total Taxes and Charges,Общие налоги и сборы, Additional Discount,Дополнительная скидка, Apply Additional Discount On,Применить дополнительную скидку на, -Additional Discount Amount (Company Currency),Сумма дополнительных скидок (валюта компании), +Additional Discount Amount (Company Currency),Сумма дополнительных скидок (в валюте компании), Additional Discount Percentage,Дополнительная скидка в процентах, Additional Discount Amount,Сумма дополнительной скидки, -Grand Total (Company Currency),Общий итог (валюта компании), -Rounding Adjustment (Company Currency),Коррекция округления (валюта компании), -Rounded Total (Company Currency),Округлые Всего (валюта компании), -In Words (Company Currency),Словами (валюта компании), +Grand Total (Company Currency),Общий итог (в валюте компании), +Rounding Adjustment (Company Currency),Коррекция округления (в валюте компании), +Rounded Total (Company Currency),Всего округленно (в валюте компании), +In Words (Company Currency),Словами (в валюте компании), Rounding Adjustment,Коррекция округления, In Words,Прописью, Total Advance,Общий аванс, Disable Rounded Total,Отключение закругленными Итого, Cash/Bank Account,Наличные / Банковский счет, -Write Off Amount (Company Currency),Сумма списаний (валюта компании), +Write Off Amount (Company Currency),Сумма списаний (в валюте компании), Set Advances and Allocate (FIFO),Установите авансы и распределите (FIFO), Get Advances Paid,Получить авансы выданные, Advances,Авансы, @@ -5055,14 +5055,14 @@ Accepted Qty,Принятое кол-во, Rejected Qty,Отклоненое кол-во, UOM Conversion Factor,Коэффициент пересчета единицы измерения, Discount on Price List Rate (%),Скидка от прайс-листа (%), -Price List Rate (Company Currency),Прайс-лист Тариф (валюта компании), +Price List Rate (Company Currency),Прайс-лист Тариф (в валюте компании), Rate ,Цена , -Rate (Company Currency),Тариф (валюта компании), -Amount (Company Currency),Сумма (валюта компании), +Rate (Company Currency),Тариф (в валюте компании), +Amount (Company Currency),Сумма (в валюте компании), Is Free Item,Это бесплатный товар, Net Rate,Нетто-ставка, -Net Rate (Company Currency),Чистая стоимость (валюта компании), -Net Amount (Company Currency),Чистая сумма (валюта компании), +Net Rate (Company Currency),Чистая стоимость (в валюте компании), +Net Amount (Company Currency),Чистая сумма (в валюте компании), Item Tax Amount Included in Value,"Сумма налога на имущество, включенная в стоимость", Landed Cost Voucher Amount,Земельные стоимости путевки сумма, Raw Materials Supplied Cost,Стоимость поставленного сырья, @@ -5135,9 +5135,9 @@ Redemption Cost Center,Центр выкупа, In Words will be visible once you save the Sales Invoice.,В записях будет видно как только вы сохраните счет продажи., Allocate Advances Automatically (FIFO),Автоматическое выделение авансов (FIFO), Get Advances Received,Получить авансы полученные, -Base Change Amount (Company Currency),Базовая Изменение Сумма (Компания Валюта), +Base Change Amount (Company Currency),Базовая Изменение Сумма (в валюте компании), Write Off Outstanding Amount,Списание суммы задолженности, -Terms and Conditions Details,Условия Подробности, +Terms and Conditions Details,Дополнительные условия, Is Internal Customer,Внутренний клиент, Is Discounted,Со скидкой, Unpaid and Discounted,Неоплачиваемый и со скидкой, @@ -5158,7 +5158,7 @@ Qty as per Stock UOM,Кол-во в соответствии с ед.измер Discount and Margin,Скидка и маржа, Rate With Margin,Оценить с маржой, Discount (%) on Price List Rate with Margin,Скидка (%) на цену Прейскурант с маржой, -Rate With Margin (Company Currency),Ставка с маржей (валюта компании), +Rate With Margin (Company Currency),Ставка с маржей (в валюте компании), Delivered By Supplier,Доставлено поставщиком, Deferred Revenue,Отложенный доход, Deferred Revenue Account,Отложенный счет доходов, @@ -5168,12 +5168,12 @@ Customer Warehouse (Optional),Склад Клиент (Необязательн Available Batch Qty at Warehouse,Доступное кол-во пакетов на складе, Available Qty at Warehouse,Доступное кол-во на складе, Delivery Note Item,Доставляемый продукт, -Base Amount (Company Currency),Базовая сумма (валюта компании), +Base Amount (Company Currency),Базовая сумма (в валюте компании), Sales Invoice Timesheet,Счет по табелю, Time Sheet,Табель учета рабочего времени, Billing Hours,Оплачеваемые часы, Timesheet Detail,Сведения о расписании, -Tax Amount After Discount Amount (Company Currency),Сумма налога после скидки Сумма (Компания валют), +Tax Amount After Discount Amount (Company Currency),Сумма налога после суммы скидки (в валюте компании), Item Wise Tax Detail,Подробная информация о налоге на товар, Parenttype,ParentType, "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like ""Shipping"", ""Insurance"", ""Handling"" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on ""Previous Row Total"" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.","Стандартный шаблон налог, который может быть применен ко всем сделок купли-продажи. Этот шаблон может содержать перечень налоговых руководителей, а также других глав расходы / доходы, как ""Shipping"", ""Insurance"", ""Обращение"" и т.д. \n\n #### Примечание \n\n ставка налога на Вы Определить здесь будет стандартная ставка налога на прибыль для всех ** деталей **. Если есть ** товары **, которые имеют различные цены, они должны быть добавлены в ** деталь налога ** стол в ** деталь ** мастера.\n\n #### Описание колонок \n\n 1. Расчет Тип: \n - Это может быть ** Чистый Всего ** (то есть сумма основной суммы).\n - ** На предыдущей строке Total / сумма ** (по совокупности налогов и сборов). Если вы выбираете эту опцию, налог будет применяться в процентах от предыдущего ряда (в налоговом таблицы) суммы или объема.\n - ** ** Фактический (как уже упоминалось).\n 2. Счет Руководитель: лицевому счету, при которых этот налог будут забронированы \n 3. Центр Стоимость: Если налог / налог на заряд доход (как перевозка груза) или расходов это должен быть забронирован на МВЗ.\n 4. Описание: Описание налога (которые будут напечатаны в счетах-фактурах / кавычек).\n 5. Оценить: Налоговая ставка.\n 6. Количество: Сумма налога.\n 7. Всего: Суммарное к этой точке.\n 8. Введите Row: Если на базе ""Предыдущая сумма по строке"" вы можете выбрать номер строки которой будет приниматься в качестве основы для такого расчета (по умолчанию предыдущего ряда).\n 9. Это налог Включено в основной ставке ?: Если вы посмотрите, это значит, что этот налог не будет показано ниже в таблице элементов, но будет включен в основной ставке в основной таблице элементов. Это полезно, если вы хотите дать квартира Цена (включая все налоги) цену к клиентам.", @@ -5264,7 +5264,7 @@ Linked Doctype,Связанный Doctype, Water Analysis,Анализ воды, Soil Analysis,Анализ почвы, Plant Analysis,Анализ растений, -Fertilizer,удобрение, +Fertilizer,Удобрение, Soil Texture,Текстура почвы, Weather,Погода, Agriculture Manager,Менеджер по развитию, @@ -5527,7 +5527,7 @@ Supplier Type,Тип поставщика, Allow Purchase Invoice Creation Without Purchase Order,Разрешить создание счета без заказа на покупку, Allow Purchase Invoice Creation Without Purchase Receipt,Разрешить создание счета без квитанции о покупке, Warn RFQs,Предупреждать о RFQ, -Warn POs,Предупредить ПО, +Warn POs,Предупреждать ПО, Prevent RFQs,Предотвращение запросов, Prevent POs,Предотвращение PO, Billing Currency,Платежная валюта, @@ -5543,9 +5543,9 @@ Statutory info and other general information about your Supplier,Уставны PUR-SQTN-.YYYY.-,PUR-SQTN-.YYYY.-, Supplier Address,Адрес поставщика, Link to material requests,Ссылка на заявки на материалы, -Rounding Adjustment (Company Currency,Корректировка округления (Валюта компании, +Rounding Adjustment (Company Currency,Корректировка округления (в валюте компании, Auto Repeat Section,Секция автоматического повтора, -Is Subcontracted,Является субподряду, +Is Subcontracted,Является субподрядом, Lead Time in days,Время выполнения в днях, Supplier Score,Оценка поставщика, Indicator Color,Цвет индикатора, @@ -5638,14 +5638,14 @@ Success Redirect URL,URL-адрес успешного перенаправле "Leave blank for home.\nThis is relative to site URL, for example ""about"" will redirect to ""https://yoursitename.com/about""","Оставьте пустым для дома. Это относительно URL сайта, например, «about» будет перенаправлен на «https://yoursitename.com/about»", Appointment Booking Slots,Назначение Бронирование Слоты, Day Of Week,День недели, -From Time ,С , +From Time ,От времени , Campaign Email Schedule,Расписание рассылки кампании, Send After (days),Отправить после (дней), Signed,подписанный, Party User,Пользователь Party, Unsigned,Неподписанный, Fulfilment Status,Статус выполнения, -N/A,Н/д, +N/A,Н/Д, Unfulfilled,Невыполненный, Partially Fulfilled,Частично выполнено, Fulfilled,Исполненная, @@ -5856,7 +5856,7 @@ Is Featured,Показано, Intro Video,Вступительное видео, Program Course,Программа курса, School House,Общежитие, -Boarding Student,Студент-пансионер, +Boarding Student,Студент-интернат, Check this if the Student is residing at the Institute's Hostel.,"Поставьте галочку, если студент проживает в общежитии института", Walking,Пешком, Institute's Bus,Автобус института, @@ -6050,7 +6050,7 @@ Shopify Settings,Shopify настройки, status html,статус html, Enable Shopify,Включить Shopify, App Type,Тип приложения, -Last Sync Datetime,Последнее время синхронизации, +Last Sync Datetime,Последняя дата синхронизации, Shop URL,URL магазина, eg: frappe.myshopify.com,например: frappe.myshopify.com, Shared secret,Общий секрет, @@ -6092,7 +6092,7 @@ Processed Files,Обработанные файлы, Parties,Стороны, UOMs,Единицы измерения, Vouchers,Ваучеры, -Round Off Account,Округление аккаунт, +Round Off Account,Округлить счет, Day Book Data,Данные Дневной Книги, Day Book Data exported from Tally that consists of all historic transactions,"Данные дневной книги, экспортированные из Tally, которые включают все исторические транзакции", Is Day Book Data Processed,Обработаны ли данные дневника, @@ -7199,11 +7199,11 @@ Scrap Items,Утилизированные продукты, Operating Cost,Эксплуатационные затраты, Raw Material Cost,Стоимость сырья, Scrap Material Cost,Лом Материал Стоимость, -Operating Cost (Company Currency),Эксплуатационные расходы (Компания Валюта), -Raw Material Cost (Company Currency),Стоимость сырья (валюта компании), -Scrap Material Cost(Company Currency),Скрапа Стоимость (Компания Валюта), +Operating Cost (Company Currency),Эксплуатационные расходы (в валюте компании), +Raw Material Cost (Company Currency),Стоимость сырья (в валюте компании), +Scrap Material Cost(Company Currency),Стоимость отходов (в валюте компании), Total Cost,Общая стоимость, -Total Cost (Company Currency),Общая стоимость (валюта компании), +Total Cost (Company Currency),Общая стоимость (в валюте компании), Materials Required (Exploded),Необходимые материалы (в разобранном), Exploded Items,Взорванные предметы, Show in Website,Показать на веб-сайте, @@ -7219,7 +7219,7 @@ Include Item In Manufacturing,Включить товар в производс BOM Item,Спецификация продукта, Item operation,Работа с элементами, Rate & Amount,Стоимость и сумма, -Basic Rate (Company Currency),Основная ставка (валюта компании), +Basic Rate (Company Currency),Основная ставка (в валюте компании), Scrap %,Лом%, Original Item,Оригинальный товар, BOM Operation,Операция спецификации, @@ -8505,15 +8505,15 @@ IRS 1099,IRS 1099, Issued Items Against Work Order,Продукты выпущенные под заказ, Projected Quantity as Source,Планируемое количество как источник, Item Balance (Simple),Остаток продукта (простой), -Item Price Stock,Цена товара, +Item Price Stock,Стоимость продукта на складе, Item Prices,Цены продукта, Item Shortage Report,Отчет о нехватке продуктов, Item Variant Details,Подробности модификации продукта, Item-wise Price List Rate,Цена продукта в прайс-листе, -Item-wise Purchase History,Пункт мудрый История покупок, -Item-wise Purchase Register,Пункт мудрый Покупка Зарегистрироваться, -Item-wise Sales History,История продаж продуктов, -Item-wise Sales Register,Пункт мудрый Продажи Зарегистрироваться, +Item-wise Purchase History,История покупок по продуктам, +Item-wise Purchase Register,Реестр покупок по продуктам, +Item-wise Sales History,История продаж по продуктам, +Item-wise Sales Register,Реестр продаж по продуктам, Items To Be Requested,Запрашиваемые продукты, Reserved,Зарезервировано, Itemwise Recommended Reorder Level,Рекомендация пополнения уровня продукта, From fb586eb7c042b6806e4651cc3a767f195c26c93c Mon Sep 17 00:00:00 2001 From: Vladislav Date: Wed, 18 May 2022 00:02:25 +0300 Subject: [PATCH 03/98] Update ru.csv --- erpnext/translations/ru.csv | 58 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 853c571dad..2ee5b3ec7e 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -2195,7 +2195,7 @@ Program in the Fee Structure and Student Group {0} are different.,Програм Program {0} does not exist.,Программа {0} не существует., Program: ,Программа: , Progress % for a task cannot be more than 100.,Готовность задачи не может превышать 100%., -Project Collaboration Invitation,Сотрудничество Приглашение проекта, +Project Collaboration Invitation,Приглашение к сотрудничеству в проекте, Project Id,Идентификатор проекта, Project Manager,Менеджер проектов, Project Name,Название проекта, @@ -3458,7 +3458,7 @@ on,вкл, {0} {1}: Customer is required against Receivable account {2},{0} {1}: Наименование клиента обязательно для Дебиторской задолженности {2}, {0} {1}: Either debit or credit amount is required for {2},{0} {1}: Требуется указать сумму дебета или кредита для {2}, {0} {1}: Supplier is required against Payable account {2},{0} {1}: Наименование поставщика обязательно для кредиторской задолженности {2}, -{0}% Billed,{0} % оплачено, +{0}% Billed,{0}% оплачено, {0}% Delivered,{0}% доставлено, "{0}: Employee email not found, hence email not sent","{0}: Адрес электронной почты сотрудника не найден, поэтому письмо не отправлено", {0}: From {0} of type {1},{0}: От {0} типа {1}, @@ -3472,7 +3472,7 @@ Completed By,Завершено, Conditions,Условия, County,Округ, Day of Week,День недели, -"Dear System Manager,","Уважаемый Менеджер системы,", +"Dear System Manager,","Уважаемый менеджер системы,", Default Value,Значение по умолчанию, Email Group,Группа электронной почты, Email Settings,Настройки электронной почты, @@ -7129,11 +7129,11 @@ Loan ,ссуда, Shortfall Time,Время нехватки, America/New_York,Америка / Триатлон, Shortfall Amount,Сумма дефицита, -Security Value ,Значение безопасности, +Security Value ,Значение безопасности , Process Loan Security Shortfall,Недостаток безопасности процесса займа, Loan To Value Ratio,Соотношение займа к стоимости, Unpledge Time,Время невыплаты, -Loan Name,Кредит Имя, +Loan Name,Название кредита, Rate of Interest (%) Yearly,Процентная ставка (%) Годовой, Penalty Interest Rate (%) Per Day,Процентная ставка штрафа (%) в день, Penalty Interest Rate is levied on the pending interest amount on a daily basis in case of delayed repayment ,Штрафная процентная ставка взимается на сумму отложенного процента на ежедневной основе в случае задержки выплаты, @@ -7220,16 +7220,16 @@ BOM Item,Спецификация продукта, Item operation,Работа с элементами, Rate & Amount,Стоимость и сумма, Basic Rate (Company Currency),Основная ставка (в валюте компании), -Scrap %,Лом%, +Scrap %,Брак %, Original Item,Оригинальный товар, BOM Operation,Операция спецификации, Operation Time ,Время операции, In minutes,В считанные минуты, Batch Size,Размер партии, -Base Hour Rate(Company Currency),Базовый час Rate (Компания Валюта), -Operating Cost(Company Currency),Эксплуатационные расходы (Компания Валюта), +Base Hour Rate(Company Currency),Базовый час Rate (в валюте компании), +Operating Cost(Company Currency),Эксплуатационные расходы (в валюте компании), BOM Scrap Item,Спецификация отходов продукта, -Basic Amount (Company Currency),Базовая сумма (Компания Валюта), +Basic Amount (Company Currency),Базовая сумма (в валюте компании), BOM Update Tool,Инструмент обновления спецификации, "Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate ""BOM Explosion Item"" table as per new BOM.\nIt also updates latest price in all the BOMs.","Замените конкретную спецификацию во всех других спецификациях, где она используется. Он заменит старую ссылку BOM, обновит стоимость и восстановит таблицу «BOM Explosion Item» в соответствии с новой спецификацией. Он также обновляет последнюю цену во всех спецификациях.", Replace BOM,Заменить спецификацию, @@ -7322,11 +7322,11 @@ This is a location where raw materials are available.,"Это место, где Work-in-Progress Warehouse,Работа-в-Прогресс Склад, This is a location where operations are executed.,"Это место, где выполняются операции.", This is a location where final product stored.,"Это место, где хранится конечный продукт.", -Scrap Warehouse,Лом Склад, +Scrap Warehouse,Склад брака, This is a location where scraped materials are stored.,"Это место, где хранятся скребки.", Required Items,Требуемые товары, -Actual Start Date,Фактическая Дата начала, -Planned End Date,Планируемая Дата завершения, +Actual Start Date,Фактическая дата начала, +Planned End Date,Планируемая дата завершения, Actual End Date,Факт. дата окончания, Operation Cost,Стоимость эксплуатации, Planned Operating Cost,Планируемые Эксплуатационные расходы, @@ -7348,9 +7348,9 @@ in Minutes,Через несколько минут, Actual Time and Cost,Фактическое время и стоимость, Actual Start Time,Фактическое время начала, Actual End Time,Фактическое время окончания, -Updated via 'Time Log',"Обновлено помощью ""Time Вход""", +Updated via 'Time Log',"Обновлено через 'Журнал времени'", Actual Operation Time,Фактическая время работы, -in Minutes\nUpdated via 'Time Log',"в минутах \n Обновлено помощью ""Time Вход""", +in Minutes\nUpdated via 'Time Log',"в минутах \n Обновлено через 'Журнал времени'", (Hour Rate / 60) * Actual Operation Time,(часовая ставка ÷ 60) × фактическое время работы, Workstation Name,Название рабочего места, Production Capacity,Производственная мощность, @@ -7377,7 +7377,7 @@ Certification Validity,Срок действия сертификации, Discuss ID,Обсудить ID, GitHub ID,Идентификатор GitHub, Non Profit Manager,Менеджер некоммерческих организаций, -Chapter Head,Глава главы, +Chapter Head,Заголовок главы, Meetup Embed HTML,Вставить HTML-код, chapters/chapter_name\nleave blank automatically set after saving chapter.,главы / chapter_name оставить пустым автоматически после сохранения главы., Chapter Members,Члены группы, @@ -7435,7 +7435,7 @@ Tag Line,Тег линии, Company Tagline for website homepage,Слоган компании на главной странице сайта, Company Description for website homepage,Описание компании на главной странице сайта, Homepage Slideshow,Слайдшоу на домашней странице, -"URL for ""All Products""",URL для "Все продукты", +"URL for ""All Products""",URL для ""Все продукты""", Products to be shown on website homepage,Продукты будут показаны на главной странице сайта, Homepage Featured Product,Рекомендуемые продукты на главной страницу, route,маршрут, @@ -7514,7 +7514,7 @@ Ignore User Time Overlap,Игнорировать перекрытие поль Ignore Employee Time Overlap,Игнорировать перекрытие времени сотрудников, Weight,Вес, Parent Task,Родительская задача, -Timeline,График, +Timeline,Хронология, Expected Time (in hours),Ожидаемое время (в часах), % Progress,% Прогресс, Is Milestone,Является этапом, @@ -7736,14 +7736,14 @@ Close Opportunity After Days,"Закрыть Выявление Через, дн Default Quotation Validity Days,"Число дней по умолчанию, в течение которых Предложение действительно", Sales Update Frequency,Частота обновления продаж, Each Transaction,Каждая транзакция, -SMS Center,СМС-центр, +SMS Center,SMS-центр, Send To,Отправить, All Contact,Всем контактам, All Customer Contact,Контакты всех клиентов, All Supplier Contact,Всем контактам поставщиков, All Sales Partner Contact,Всем контактам торговых партнеров, -All Lead (Open),Всем лидам (Созданным), -All Employee (Active),Всем сотрудникам (Активным), +All Lead (Open),Всем лидам (Открыт), +All Employee (Active),Всем сотрудникам (Активный), All Sales Person,Всем продавцам, Create Receiver List,Создать список получателей, Receiver List,Список получателей, @@ -7792,7 +7792,7 @@ Exchange Gain / Loss Account,Обмен Прибыль / убытках, Unrealized Exchange Gain/Loss Account,Нереализованная учетная ставка по обмену / убытку, Allow Account Creation Against Child Company,Разрешить создание аккаунта против дочерней компании, Default Payable Account,По умолчанию оплачивается аккаунт, -Default Employee Advance Account,Default Advance Account, +Default Employee Advance Account,Авансовый счет сотрудника по умолчанию, Default Cost of Goods Sold Account,По умолчанию Себестоимость проданных товаров счет, Default Income Account,Счет дохода по умолчанию, Default Deferred Revenue Account,По умолчанию отложенная учетная запись, @@ -8219,7 +8219,7 @@ Price List Master,Прайс-лист Мастер, Price List Name,Название прайс-листа, Price Not UOM Dependent,Цена не зависит от UOM, Applicable for Countries,Применимо для стран, -Price List Country,Цены Страна, +Price List Country,Прайс лист страны, MAT-PRE-.YYYY.-,MAT-PRE-.YYYY.-, Supplier Delivery Note,Доставочный лист, Time at which materials were received,Время получения материалов, @@ -8327,7 +8327,7 @@ Outgoing Rate,Исходящие Оценить, Actual Qty After Transaction,Остаток после проведения, Stock Value Difference,Расхождение стоимости запасов, Stock Queue (FIFO),Фото со Очередь (FIFO), -Is Cancelled,Является Отмененные, +Is Cancelled,Является отмененным, Stock Reconciliation,Инвентаризация запасов, This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.,"Этот инструмент поможет вам обновить или исправить количество и оценку запасов в системе. Это, как правило, используется для синхронизации системных значений и то, что на самом деле существует в ваших складах.", MAT-RECO-.YYYY.-,MAT-RECO-.YYYY.-, @@ -9045,7 +9045,7 @@ Send Membership Acknowledgement,Отправить подтверждение ч Send Invoice with Email,Отправить счет по электронной почте, Membership Print Format,Формат печати членства, Invoice Print Format,Формат печати счета, -Revoke ,Отозвать<Key></Key>, +Revoke ,Отозвать , You can learn more about memberships in the manual. ,Вы можете узнать больше о членстве в руководстве., ERPNext Docs,ERPСледующие документы, Regenerate Webhook Secret,Восстановить секрет веб-перехватчика, @@ -9080,7 +9080,7 @@ Additional Salary ,Дополнительная зарплата , Unmarked days,Неотмеченные дни, Absent Days,Отсутствующие дни, Conditions and Formula variable and example,"Условия и формула, переменная и пример", -Feedback By,Отзыв Автор, +Feedback By,Отзыв от, Manufacturing Section,Производственный отдел, "By default, the Customer Name is set as per the Full Name entered. If you want Customers to be named by a ","По умолчанию имя клиента устанавливается в соответствии с введенным полным именем. Если вы хотите, чтобы имена клиентов", Configure the default Price List when creating a new Sales transaction. Item prices will be fetched from this Price List.,Настройте прайс-лист по умолчанию при создании новой транзакции продаж. Цены на товары будут взяты из этого прейскуранта., @@ -9322,7 +9322,7 @@ End Date must not be lesser than Start Date,Дата окончания не д Employee {0} already has Active Shift {1}: {2},Сотрудник {0} уже имеет активную смену {1}: {2}, from {0},от {0}, to {0},в {0}, -Please select Employee first.,"Пожалуйста, сначала выберите Сотрудник.", +Please select Employee first.,"Пожалуйста, сначала выберите сотрудника.", Please set {0} for the Employee or for Department: {1},Установите {0} для сотрудника или отдела: {1}, To Date should be greater than From Date,"Дата до должна быть больше, чем Дата", Employee Onboarding: {0} is already for Job Applicant: {1},Прием на работу сотрудника: {0} уже для соискателя: {1}, @@ -9335,7 +9335,7 @@ Asset Value Analytics,Аналитика стоимости активов, Category-wise Asset Value,Стоимость актива по категориям, Total Assets,Итого активы, New Assets (This Year),Новые активы (в этом году), -Row #{}: Depreciation Posting Date should not be equal to Available for Use Date.,Строка № {}: Дата проводки амортизации не должна совпадать с датой доступности для использования., +Row #{}: Depreciation Posting Date should not be equal to Available for Use Date.,Строка №{}: Дата проводки амортизации не должна совпадать с датой доступности для использования., Incorrect Date,Неправильная дата, Invalid Gross Purchase Amount,Неверная сумма покупки брутто, There are active maintenance or repairs against the asset. You must complete all of them before cancelling the asset.,"Активно проводится техническое обслуживание или ремонт актива. Вы должны выполнить их все, прежде чем аннулировать актив.", @@ -9383,7 +9383,7 @@ You can only select one mode of payment as default,По умолчанию вы Missing Account,Отсутствует аккаунт, Customers not selected.,Клиенты не выбраны., Statement of Accounts,Выписка со счетов, -Ageing Report Based On ,Отчет о старении на основе, +Ageing Report Based On ,Отчет о старении на основе , Please enter distributed cost center,"Пожалуйста, введите распределенное МВЗ", Total percentage allocation for distributed cost center should be equal to 100,Общее процентное распределение для распределенного МВЗ должно быть равно 100., Cannot enable Distributed Cost Center for a Cost Center already allocated in another Distributed Cost Center,"Невозможно включить центр распределенных затрат для центра затрат, уже выделенного в другом центре распределенных затрат", @@ -9489,7 +9489,7 @@ Mandatory Results,Обязательные результаты, Sales Invoice or Patient Encounter is required to create Lab Tests,Счет продажи или встреча с пациентом необходимы для создания лабораторных тестов, Insufficient Data,Недостаточные данные, Lab Test(s) {0} created successfully,Лабораторные тесты {0} успешно созданы, -Test :,Контрольная работа :, +Test :,Тест :, Sample Collection {0} has been created,Коллекция образцов {0} создана, Normal Range: ,Нормальный диапазон:, Row #{0}: Check Out datetime cannot be less than Check In datetime,Строка № {0}: Дата и время выезда не может быть меньше даты и времени выезда., From 77db843692118a06a491ff4add2085e14a128030 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 5 May 2022 19:41:05 +0530 Subject: [PATCH 04/98] refactor!: Make required changes to create SCO from PO --- .../doctype/purchase_order/purchase_order.js | 17 ++++ .../purchase_order/purchase_order.json | 28 +++--- .../doctype/purchase_order/purchase_order.py | 63 ++++++++++++- .../purchase_order_dashboard.py | 2 +- .../purchase_order_item.json | 26 +++++- erpnext/controllers/buying_controller.py | 5 +- erpnext/public/js/controllers/buying.js | 2 +- erpnext/public/js/utils.js | 2 +- erpnext/stock/doctype/bin/bin.py | 28 +++--- .../stock/doctype/stock_entry/stock_entry.py | 91 +++++++++++-------- erpnext/stock/get_item_details.py | 4 +- 11 files changed, 182 insertions(+), 86 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 2005dac37d..1f6de1aa7b 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -28,6 +28,11 @@ frappe.ui.form.on("Purchase Order", { } }); + frm.set_query("fg_item", "items", function() { + return { + filters: {'is_sub_contracted_item': 1} + } + }); }, company: function(frm) { @@ -109,6 +114,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e 'Purchase Invoice': 'Purchase Invoice', 'Stock Entry': 'Material to Supplier', 'Payment Entry': 'Payment', + 'Subcontracting Order': 'Subcontracting Order' } super.setup(); @@ -183,6 +189,9 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e cur_frm.add_custom_button(__('Material to Supplier'), function() { me.make_stock_entry(); }, __("Transfer")); } + if (doc.is_subcontracted) { + cur_frm.add_custom_button(__('Subcontracting Order'), this.make_subcontracting_order, __('Create')); + } } if(flt(doc.per_billed) < 100) cur_frm.add_custom_button(__('Purchase Invoice'), @@ -407,6 +416,14 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e }) } + make_subcontracting_order() { + frappe.model.open_mapped_doc({ + method: "erpnext.buying.doctype.purchase_order.purchase_order.make_subcontracting_order", + frm: cur_frm, + freeze_message: __("Creating Subcontracting Order ...") + }) + } + add_from_mappers() { var me = this; this.frm.add_custom_button(__('Material Request'), diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 896208f25e..307b57607e 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -16,6 +16,8 @@ "supplier_name", "apply_tds", "tax_withholding_category", + "is_subcontracted", + "supplier_warehouse", "column_break1", "company", "transaction_date", @@ -51,10 +53,7 @@ "price_list_currency", "plc_conversion_rate", "ignore_pricing_rule", - "sec_warehouse", - "is_subcontracted", - "col_break_warehouse", - "supplier_warehouse", + "section_break_45", "before_items_section", "scan_barcode", "items_col_break", @@ -154,7 +153,8 @@ "hidden": 1, "label": "Title", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "reqd": 1 }, { "fieldname": "naming_series", @@ -439,11 +439,6 @@ "permlevel": 1, "print_hide": 1 }, - { - "fieldname": "sec_warehouse", - "fieldtype": "Section Break", - "label": "Subcontracting" - }, { "description": "Sets 'Warehouse' in each row of the Items table.", "fieldname": "set_warehouse", @@ -452,15 +447,10 @@ "options": "Warehouse", "print_hide": 1 }, - { - "fieldname": "col_break_warehouse", - "fieldtype": "Column Break" - }, { "default": "No", "fieldname": "is_subcontracted", "fieldtype": "Select", - "in_standard_filter": 1, "label": "Supply Raw Materials", "options": "No\nYes", "print_hide": 1 @@ -1138,16 +1128,21 @@ "fieldtype": "Link", "label": "Tax Withholding Category", "options": "Tax Withholding Category" + }, + { + "fieldname": "section_break_45", + "fieldtype": "Section Break" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-09-28 13:10:47.955401", + "modified": "2022-04-26 18:46:58.863174", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -1194,6 +1189,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "timeline_field": "supplier", "title_field": "supplier_name", "track_changes": 1 diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 582bd8d1db..e8b8b87b98 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -69,7 +69,7 @@ class PurchaseOrder(BuyingController): self.validate_with_previous_doc() self.validate_for_subcontracting() self.validate_minimum_order_qty() - self.validate_bom_for_subcontracting_items() + self.validate_fg_item_for_subcontracting() self.create_raw_materials_supplied("supplied_items") self.set_received_qty_for_drop_ship_items() validate_inter_company_party( @@ -193,12 +193,25 @@ class PurchaseOrder(BuyingController): ).format(item_code, qty, itemwise_min_order_qty.get(item_code)) ) - def validate_bom_for_subcontracting_items(self): - if self.is_subcontracted == "Yes": + def validate_fg_item_for_subcontracting(self): + if self.is_subcontracted: for item in self.items: - if not item.bom: + if not item.fg_item: frappe.throw( - _("BOM is not specified for subcontracting item {0} at row {1}").format( + _("Finished Good Item is not specified for service item {0} at row {1}").format( + item.item_code, item.idx + ) + ) + else: + if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"): + frappe.throw( + _( + "Finished Good Item {0} must be a sub-contracted item for service item {1} at row {2}" + ).format(item.fg_item, item.item_code, item.idx) + ) + if not item.fg_item_qty: + frappe.throw( + _("Finished Good Item Qty is not specified for service item {0} at row {1}").format( item.item_code, item.idx ) ) @@ -746,3 +759,43 @@ def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None): "serial_no": "\n".join(row.serial_no) if row.serial_no else "", } ) + + +@frappe.whitelist() +def make_subcontracting_order(source_name, target_doc=None): + return get_mapped_subcontracting_order(source_name, target_doc) + + +def get_mapped_subcontracting_order(source_name, target_doc=None): + + if target_doc and isinstance(target_doc, str): + target_doc = json.loads(target_doc) + for key in ["service_items", "items", "supplied_items"]: + if key in target_doc: + del target_doc[key] + target_doc = json.dumps(target_doc) + + target_doc = get_mapped_doc( + "Purchase Order", + source_name, + { + "Purchase Order": { + "doctype": "Subcontracting Order", + "field_map": {}, + "field_no_map": ["total_qty", "total", "net_total"], + "validation": { + "docstatus": ["=", 1], + }, + }, + "Purchase Order Item": { + "doctype": "Subcontracting Order Service Item", + "field_map": {}, + "field_no_map": [], + }, + }, + target_doc, + ) + + target_doc.populate_items_table() + + return target_doc diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py index 81f20100c3..0c38c3e8da 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py @@ -22,6 +22,6 @@ def get_data(): "label": _("Reference"), "items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"], }, - {"label": _("Sub-contracting"), "items": ["Stock Entry"]}, + {"label": _("Sub-contracting"), "items": ["Subcontracting Order"]}, ], } diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index a18c527644..b4cdb18211 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -11,6 +11,8 @@ "supplier_part_no", "item_name", "product_bundle", + "fg_item", + "fg_item_qty", "column_break_4", "schedule_date", "expected_delivery_date", @@ -572,18 +574,18 @@ "read_only": 1 }, { - "depends_on": "eval:parent.is_subcontracted == 'Yes'", "fieldname": "bom", "fieldtype": "Link", "label": "BOM", "options": "BOM", - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "default": "0", - "depends_on": "eval:parent.is_subcontracted == 'Yes'", "fieldname": "include_exploded_items", "fieldtype": "Check", + "hidden": 1, "label": "Include Exploded Items", "print_hide": 1 }, @@ -845,13 +847,29 @@ "label": "Sales Order Packed Item", "no_copy": 1, "print_hide": 1 + }, + { + "depends_on": "eval:parent.is_subcontracted == 'Yes'", + "fieldname": "fg_item", + "fieldtype": "Link", + "label": "Finished Good Item", + "mandatory_depends_on": "eval:parent.is_subcontracted == 'Yes'", + "options": "Item" + }, + { + "default": "1", + "depends_on": "eval:parent.is_subcontracted == 'Yes'", + "fieldname": "fg_item_qty", + "fieldtype": "Float", + "label": "Finished Good Item Qty", + "mandatory_depends_on": "eval:parent.is_subcontracted == 'Yes'" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-02-02 13:10:18.398976", + "modified": "2022-04-07 14:53:16.684010", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 47892073f3..4823e8b05c 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -803,10 +803,7 @@ class BuyingController(StockController, Subcontracting): if self.doctype == "Material Request": return - if hasattr(self, "is_subcontracted") and self.is_subcontracted == "Yes": - validate_item_type(self, "is_sub_contracted_item", "subcontracted") - else: - validate_item_type(self, "is_purchase_item", "purchase") + validate_item_type(self, "is_purchase_item", "purchase") def get_asset_item_details(asset_items): diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 54e5daa6bd..a925470d60 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -84,7 +84,7 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac if (me.frm.doc.is_subcontracted == "Yes") { return{ query: "erpnext.controllers.queries.item_query", - filters:{ 'supplier': me.frm.doc.supplier, 'is_sub_contracted_item': 1 } + filters:{ 'supplier': me.frm.doc.supplier, 'is_stock_item': 0 } } } else { diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 9339c5d998..8260426704 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -484,7 +484,7 @@ erpnext.utils.update_child_items = function(opts) { filters = {"is_sales_item": 1}; } else if (frm.doc.doctype == 'Purchase Order') { if (frm.doc.is_subcontracted == "Yes") { - filters = {"is_sub_contracted_item": 1}; + filters = {"is_stock_item": 0}; } else { filters = {"is_purchase_item": 1}; } diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 6cb9f7e479..a66e6f85f6 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -43,20 +43,19 @@ class Bin(Document): def update_reserved_qty_for_sub_contracting(self): # reserved qty - po = frappe.qb.DocType("Purchase Order") - supplied_item = frappe.qb.DocType("Purchase Order Item Supplied") + sco = frappe.qb.DocType("Subcontracting Order") + supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item") reserved_qty_for_sub_contract = ( - frappe.qb.from_(po) + frappe.qb.from_(sco) .from_(supplied_item) .select(Sum(Coalesce(supplied_item.required_qty, 0))) .where( (supplied_item.rm_item_code == self.item_code) - & (po.name == supplied_item.parent) - & (po.docstatus == 1) - & (po.is_subcontracted == "Yes") - & (po.status != "Closed") - & (po.per_received < 100) + & (sco.name == supplied_item.parent) + & (sco.docstatus == 1) + & (sco.status != "Closed") + & (sco.per_received < 100) & (supplied_item.reserve_warehouse == self.warehouse) ) ).run()[0][0] or 0.0 @@ -67,21 +66,20 @@ class Bin(Document): materials_transferred = ( frappe.qb.from_(se) .from_(se_item) - .from_(po) + .from_(sco) .select( Sum(Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty)) ) .where( (se.docstatus == 1) & (se.purpose == "Send to Subcontractor") - & (Coalesce(se.purchase_order, "") != "") + & (Coalesce(se.subcontracting_order, "") != "") & ((se_item.item_code == self.item_code) | (se_item.original_item == self.item_code)) & (se.name == se_item.parent) - & (po.name == se.purchase_order) - & (po.docstatus == 1) - & (po.is_subcontracted == "Yes") - & (po.status != "Closed") - & (po.per_received < 100) + & (sco.name == se.subcontracting_order) + & (sco.docstatus == 1) + & (sco.status != "Closed") + & (sco.per_received < 100) ) ).run()[0][0] or 0.0 diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 1e624714d0..b851795389 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -134,8 +134,8 @@ class StockEntry(StockController): update_serial_nos_after_submit(self, "items") self.update_work_order() - self.validate_purchase_order() - self.update_purchase_order_supplied_items() + self.validate_subcontracting_order() + self.update_subcontracting_order_supplied_items() self.make_gl_entries() @@ -154,7 +154,7 @@ class StockEntry(StockController): self.set_material_request_transfer_status("Completed") def on_cancel(self): - self.update_purchase_order_supplied_items() + self.update_subcontracting_order_supplied_items() if self.work_order and self.purpose == "Material Consumption for Manufacture": self.validate_work_order_status() @@ -810,8 +810,8 @@ class StockEntry(StockController): serial_nos.append(sn) - def validate_purchase_order(self): - """Throw exception if more raw material is transferred against Purchase Order than in + def validate_subcontracting_order(self): + """Throw exception if more raw material is transferred against Subcontracting Order than in the raw materials supplied table""" backflush_raw_materials_based_on = frappe.db.get_single_value( "Buying Settings", "backflush_raw_materials_of_subcontract_based_on" @@ -819,24 +819,28 @@ class StockEntry(StockController): qty_allowance = flt(frappe.db.get_single_value("Buying Settings", "over_transfer_allowance")) - if not (self.purpose == "Send to Subcontractor" and self.purchase_order): + if not (self.purpose == "Send to Subcontractor" and self.subcontracting_order): return if backflush_raw_materials_based_on == "BOM": - purchase_order = frappe.get_doc("Purchase Order", self.purchase_order) + subcontracting_order = frappe.get_doc("Subcontracting Order", self.subcontracting_order) for se_item in self.items: item_code = se_item.original_item or se_item.item_code precision = cint(frappe.db.get_default("float_precision")) or 3 required_qty = sum( - [flt(d.required_qty) for d in purchase_order.supplied_items if d.rm_item_code == item_code] + [ + flt(d.required_qty) + for d in subcontracting_order.supplied_items + if d.rm_item_code == item_code + ] ) total_allowed = required_qty + (required_qty * (qty_allowance / 100)) if not required_qty: bom_no = frappe.db.get_value( - "Purchase Order Item", - {"parent": self.purchase_order, "item_code": se_item.subcontracted_item}, + "Subcontracting Order Item", + {"parent": self.subcontracting_order, "item_code": se_item.subcontracted_item}, "bom", ) @@ -848,7 +852,7 @@ class StockEntry(StockController): required_qty = sum( [ flt(d.required_qty) - for d in purchase_order.supplied_items + for d in subcontracting_order.supplied_items if d.rm_item_code == original_item_code ] ) @@ -857,26 +861,39 @@ class StockEntry(StockController): if not required_qty: frappe.throw( - _("Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1}").format( - se_item.item_code, self.purchase_order + _("Item {0} not found in 'Raw Materials Supplied' table in Subcontracting Order {1}").format( + se_item.item_code, self.subcontracting_order ) ) total_supplied = frappe.db.sql( """select sum(transfer_qty) from `tabStock Entry Detail`, `tabStock Entry` - where `tabStock Entry`.purchase_order = %s + where `tabStock Entry`.subcontracting_order = %s and `tabStock Entry`.docstatus = 1 and `tabStock Entry Detail`.item_code = %s and `tabStock Entry Detail`.parent = `tabStock Entry`.name""", - (self.purchase_order, se_item.item_code), + (self.subcontracting_order, se_item.item_code), )[0][0] if flt(total_supplied, precision) > flt(total_allowed, precision): frappe.throw( - _("Row {0}# Item {1} cannot be transferred more than {2} against Purchase Order {3}").format( - se_item.idx, se_item.item_code, total_allowed, self.purchase_order + _( + "Row {0}# Item {1} cannot be transferred more than {2} against Subcontracting Order {3}" + ).format( + se_item.idx, se_item.item_code, total_allowed, self.subcontracting_order ) ) + elif not se_item.sco_rm_detail: + filters = { + "parent": self.subcontracting_order, + "docstatus": 1, + "rm_item_code": se_item.item_code, + "main_item_code": se_item.subcontracted_item, + } + + sco_rm_detail = frappe.db.get_value("Subcontracting Order Supplied Item", filters, "name") + if sco_rm_detail: + se_item.db_set("sco_rm_detail", sco_rm_detail) elif backflush_raw_materials_based_on == "Material Transferred for Subcontract": for row in self.items: if not row.subcontracted_item: @@ -885,17 +902,17 @@ class StockEntry(StockController): row.idx, frappe.bold(row.item_code) ) ) - elif not row.po_detail: + elif not row.sco_rm_detail: filters = { - "parent": self.purchase_order, + "parent": self.subcontracting_order, "docstatus": 1, "rm_item_code": row.item_code, "main_item_code": row.subcontracted_item, } - po_detail = frappe.db.get_value("Purchase Order Item Supplied", filters, "name") - if po_detail: - row.db_set("po_detail", po_detail) + sco_rm_detail = frappe.db.get_value("Subcontracting Order Supplied Item", filters, "name") + if sco_rm_detail: + row.db_set("sco_rm_detail", sco_rm_detail) def validate_bom(self): for d in self.get("items"): @@ -1901,7 +1918,7 @@ class StockEntry(StockController): se_child.is_process_loss = item_row.get("is_process_loss", 0) for field in [ - "po_detail", + "sco_rm_detail", "original_item", "expense_account", "description", @@ -1975,26 +1992,26 @@ class StockEntry(StockController): else: frappe.throw(_("Batch {0} of Item {1} is disabled.").format(item.batch_no, item.item_code)) - def update_purchase_order_supplied_items(self): - if self.purchase_order and ( + def update_subcontracting_order_supplied_items(self): + if self.subcontracting_order and ( self.purpose in ["Send to Subcontractor", "Material Transfer"] or self.is_return ): - # Get PO Supplied Items Details + # Get SCO Supplied Items Details item_wh = frappe._dict( frappe.db.sql( """ select rm_item_code, reserve_warehouse - from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup - where po.name = poitemsup.parent - and po.name = %s""", - self.purchase_order, + from `tabSubcontracting Order` sco, `tabSubcontracting Order Supplied Item` scoitemsup + where sco.name = scoitemsup.parent + and sco.name = %s""", + self.subcontracting_order, ) ) - supplied_items = get_supplied_items(self.purchase_order) + supplied_items = get_supplied_items(self.subcontracting_order) for name, item in supplied_items.items(): - frappe.db.set_value("Purchase Order Item Supplied", name, item) + frappe.db.set_value("Subcontracting Order Supplied Item", name, item) # Update reserved sub contracted quantity in bin based on Supplied Item Details and for d in self.get("items"): @@ -2479,25 +2496,25 @@ def validate_sample_quantity(item_code, sample_quantity, qty, batch_no=None): return sample_quantity -def get_supplied_items(purchase_order): +def get_supplied_items(subcontracting_order): fields = [ "`tabStock Entry Detail`.`transfer_qty`", "`tabStock Entry`.`is_return`", - "`tabStock Entry Detail`.`po_detail`", + "`tabStock Entry Detail`.`sco_rm_detail`", "`tabStock Entry Detail`.`item_code`", ] filters = [ ["Stock Entry", "docstatus", "=", 1], - ["Stock Entry", "purchase_order", "=", purchase_order], + ["Stock Entry", "subcontracting_order", "=", subcontracting_order], ] supplied_item_details = {} for row in frappe.get_all("Stock Entry", fields=fields, filters=filters): - if not row.po_detail: + if not row.sco_rm_detail: continue - key = row.po_detail + key = row.sco_rm_detail if key not in supplied_item_details: supplied_item_details.setdefault( key, frappe._dict({"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0}) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index f72588e034..0d7d47262a 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -237,8 +237,8 @@ def validate_item_details(args, item): throw(_("Item {0} is a template, please select one of its variants").format(item.name)) elif args.transaction_type == "buying" and args.doctype != "Material Request": - if args.get("is_subcontracted") == "Yes" and item.is_sub_contracted_item != 1: - throw(_("Item {0} must be a Sub-contracted Item").format(item.name)) + if args.get("is_subcontracted") == "Yes" and item.is_stock_item: + throw(_("Item {0} must be a Non-Stock Item").format(item.name)) def get_basic_details(args, item, overwrite_warehouse=True): From 68c21d9895d0daf77f5360f189eef70835069ddc Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 26 Apr 2022 17:02:12 +0530 Subject: [PATCH 05/98] feat: Add fields "subcontracting_order" and "sco_rm_detail" in SE and SE Detail --- erpnext/stock/doctype/stock_entry/stock_entry.json | 8 ++++++++ .../doctype/stock_entry_detail/stock_entry_detail.json | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index c38dfaa1c8..7b9eccd4aa 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -15,6 +15,7 @@ "add_to_transit", "work_order", "purchase_order", + "subcontracting_order", "delivery_note_no", "sales_invoice_no", "pick_list", @@ -153,6 +154,13 @@ "label": "Purchase Order", "options": "Purchase Order" }, + { + "depends_on": "eval:doc.purpose==\"Send to Subcontractor\"", + "fieldname": "subcontracting_order", + "fieldtype": "Link", + "label": "Subcontracting Order", + "options": "Subcontracting Order" + }, { "depends_on": "eval:doc.purpose==\"Sales Return\"", "fieldname": "delivery_note_no", diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 83aed904dd..efde46d060 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -68,6 +68,7 @@ "against_stock_entry", "ste_detail", "po_detail", + "sco_rm_detail", "putaway_rule", "column_break_51", "reference_purchase_receipt", @@ -493,6 +494,15 @@ "print_hide": 1, "read_only": 1 }, + { + "fieldname": "sco_rm_detail", + "fieldtype": "Data", + "hidden": 1, + "label": "SCO Supplied Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, { "default": "0", "depends_on": "eval:parent.purpose===\"Repack\" && doc.t_warehouse", From 29a1cb89c245f68a4db6190ccd2d83ea91c348fe Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 26 Apr 2022 17:29:45 +0530 Subject: [PATCH 06/98] feat: SubcontractingController --- .../controllers/subcontracting_controller.py | 641 ++++++++++++++++++ 1 file changed, 641 insertions(+) create mode 100644 erpnext/controllers/subcontracting_controller.py diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py new file mode 100644 index 0000000000..b393737740 --- /dev/null +++ b/erpnext/controllers/subcontracting_controller.py @@ -0,0 +1,641 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import copy +from collections import defaultdict + +import frappe +from frappe import _ +from frappe.utils import cint, cstr, flt, get_link_to_form + +from erpnext.controllers.stock_controller import StockController +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.stock.utils import get_incoming_rate + + +class SubcontractingController(StockController): + def before_validate(self): + self.remove_empty_rows() + self.set_items_conversion_factor() + + def validate(self): + self.validate_items() + self.create_raw_materials_supplied() + + def remove_empty_rows(self): + for key in ["service_items", "items", "supplied_items"]: + if self.get(key): + idx = 1 + for item in self.get(key)[:]: + if not (item.get("item_code") or item.get("main_item_code")): + self.get(key).remove(item) + else: + item.idx = idx + idx += 1 + + def set_items_conversion_factor(self): + for item in self.get("items"): + if not item.conversion_factor: + item.conversion_factor = 1 + + def validate_items(self): + for item in self.items: + if not frappe.get_value("Item", item.item_code, "is_sub_contracted_item"): + msg = f"Item {item.item_name} must be a subcontracted item." + frappe.throw(_(msg)) + if item.bom: + bom = frappe.get_doc("BOM", item.bom) + if not bom.is_active: + msg = f"Please select an active BOM for Item {item.item_name}." + frappe.throw(_(msg)) + if bom.item != item.item_code: + msg = f"Please select an valid BOM for Item {item.item_name}." + frappe.throw(_(msg)) + + def __get_data_before_save(self): + item_dict = {} + if self.doctype == "Subcontracting Receipt" and self._doc_before_save: + for row in self._doc_before_save.get("items"): + item_dict[row.name] = (row.item_code, row.qty) + + return item_dict + + def __identify_change_in_item_table(self): + self.__changed_name = [] + self.__reference_name = [] + + if self.doctype == "Subcontracting Order" or self.is_new(): + self.set(self.raw_material_table, []) + return + + item_dict = self.__get_data_before_save() + if not item_dict: + return True + + for row in self.items: + self.__reference_name.append(row.name) + if (row.name not in item_dict) or (row.item_code, row.qty) != item_dict[row.name]: + self.__changed_name.append(row.name) + + if item_dict.get(row.name): + del item_dict[row.name] + + self.__changed_name.extend(item_dict.keys()) + + def __get_backflush_based_on(self): + self.backflush_based_on = frappe.db.get_single_value( + "Buying Settings", "backflush_raw_materials_of_subcontract_based_on" + ) + + def initialized_fields(self): + self.available_materials = frappe._dict() + self.__transferred_items = frappe._dict() + self.alternative_item_details = frappe._dict() + self.__get_backflush_based_on() + + def __get_subcontracting_orders(self): + self.subcontracting_orders = [] + + if self.doctype == "Subcontracting Order": + return + + self.subcontracting_orders = [ + item.subcontracting_order for item in self.items if item.subcontracting_order + ] + + def __get_pending_qty_to_receive(self): + """Get qty to be received against the subcontracting order.""" + + self.qty_to_be_received = defaultdict(float) + + if ( + self.doctype != "Subcontracting Order" + and self.backflush_based_on != "BOM" + and self.subcontracting_orders + ): + for row in frappe.get_all( + "Subcontracting Order Item", + fields=["item_code", "(qty - received_qty) as qty", "parent", "name"], + filters={"docstatus": 1, "parent": ("in", self.subcontracting_orders)}, + ): + + self.qty_to_be_received[(row.item_code, row.parent)] += row.qty + + def __get_transferred_items(self): + fields = ["`tabStock Entry`.`subcontracting_order`"] + alias_dict = { + "item_code": "rm_item_code", + "subcontracted_item": "main_item_code", + "basic_rate": "rate", + } + + child_table_fields = [ + "item_code", + "item_name", + "description", + "qty", + "basic_rate", + "amount", + "serial_no", + "uom", + "subcontracted_item", + "stock_uom", + "batch_no", + "conversion_factor", + "s_warehouse", + "t_warehouse", + "item_group", + "sco_rm_detail", + ] + + if self.backflush_based_on == "BOM": + child_table_fields.append("original_item") + + for field in child_table_fields: + fields.append(f"`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}") + + filters = [ + ["Stock Entry", "docstatus", "=", 1], + ["Stock Entry", "purpose", "=", "Send to Subcontractor"], + ["Stock Entry", "subcontracting_order", "in", self.subcontracting_orders], + ] + + return frappe.get_all("Stock Entry", fields=fields, filters=filters) + + def __set_alternative_item_details(self, row): + if row.get("original_item"): + self.alternative_item_details[row.get("original_item")] = row + + def __get_received_items(self, doctype): + fields = [] + self.sco_field = "subcontracting_order" + + for field in ["name", self.sco_field, "parent"]: + fields.append(f"`tab{doctype} Item`.`{field}`") + + filters = [ + [doctype, "docstatus", "=", 1], + [f"{doctype} Item", self.sco_field, "in", self.subcontracting_orders], + ] + + return frappe.get_all(f"{doctype}", fields=fields, filters=filters) + + def __get_consumed_items(self, doctype, scr_items): + return frappe.get_all( + "Subcontracting Receipt Supplied Item", + fields=[ + "serial_no", + "rm_item_code", + "reference_name", + "batch_no", + "consumed_qty", + "main_item_code", + ], + filters={"docstatus": 1, "reference_name": ("in", list(scr_items)), "parenttype": doctype}, + ) + + def __update_consumed_materials(self, doctype, return_consumed_items=False): + """Deduct the consumed materials from the available materials.""" + + scr_items = self.__get_received_items(doctype) + if not scr_items: + return ([], {}) if return_consumed_items else None + + scr_items = { + item.name: item.get(self.get("sco_field") or "subcontracting_order") for item in scr_items + } + consumed_materials = self.__get_consumed_items(doctype, scr_items.keys()) + + if return_consumed_items: + return (consumed_materials, scr_items) + + for row in consumed_materials: + key = (row.rm_item_code, row.main_item_code, scr_items.get(row.reference_name)) + if not self.available_materials.get(key): + continue + + self.available_materials[key]["qty"] -= row.consumed_qty + if row.serial_no: + self.available_materials[key]["serial_no"] = list( + set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no)) + ) + + if row.batch_no: + self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty + + def get_available_materials(self): + """Get the available raw materials which has been transferred to the supplier. + available_materials = { + (item_code, subcontracted_item, subcontracting_order): { + 'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details + } + } + """ + if not self.subcontracting_orders: + return + + for row in self.__get_transferred_items(): + key = (row.rm_item_code, row.main_item_code, row.subcontracting_order) + + if key not in self.available_materials: + self.available_materials.setdefault( + key, + frappe._dict( + { + "qty": 0, + "serial_no": [], + "batch_no": defaultdict(float), + "item_details": row, + "sco_rm_details": [], + } + ), + ) + + details = self.available_materials[key] + details.qty += row.qty + details.sco_rm_details.append(row.sco_rm_detail) + + if row.serial_no: + details.serial_no.extend(get_serial_nos(row.serial_no)) + + if row.batch_no: + details.batch_no[row.batch_no] += row.qty + + self.__set_alternative_item_details(row) + + self.__transferred_items = copy.deepcopy(self.available_materials) + self.__update_consumed_materials("Subcontracting Receipt") + + def __remove_changed_rows(self): + if not self.__changed_name: + return + + i = 1 + self.set(self.raw_material_table, []) + for item in self._doc_before_save.supplied_items: + if item.reference_name in self.__changed_name: + continue + + if item.reference_name not in self.__reference_name: + continue + + item.idx = i + self.append("supplied_items", item) + + i += 1 + + def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0): + doctype = "BOM Item" if not exploded_item else "BOM Explosion Item" + fields = [f"`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit"] + + alias_dict = { + "item_code": "rm_item_code", + "name": "bom_detail_no", + "source_warehouse": "reserve_warehouse", + } + for field in [ + "item_code", + "name", + "rate", + "stock_uom", + "source_warehouse", + "description", + "item_name", + "stock_uom", + ]: + fields.append(f"`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}") + + filters = [ + [doctype, "parent", "=", bom_no], + [doctype, "docstatus", "=", 1], + ["BOM", "item", "=", item_code], + [doctype, "sourced_by_supplier", "=", 0], + ] + + return ( + frappe.get_all("BOM", fields=fields, filters=filters, order_by=f"`tab{doctype}`.`idx`") or [] + ) + + def __update_reserve_warehouse(self, row, item): + if self.doctype == "Subcontracting Order": + row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse + + def __set_alternative_item(self, bom_item): + if self.alternative_item_details.get(bom_item.rm_item_code): + bom_item.update(self.alternative_item_details[bom_item.rm_item_code]) + + def __set_serial_nos(self, item_row, rm_obj): + key = (rm_obj.rm_item_code, item_row.item_code, item_row.subcontracting_order) + if self.available_materials.get(key) and self.available_materials[key]["serial_no"]: + used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)] + rm_obj.serial_no = "\n".join(used_serial_nos) + + # Removed the used serial nos from the list + for sn in used_serial_nos: + self.available_materials[key]["serial_no"].remove(sn) + + def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty): + rm_obj.update( + { + "consumed_qty": qty, + "batch_no": batch_no, + "required_qty": qty, + "subcontracting_order": item_row.subcontracting_order, + } + ) + + self.__set_serial_nos(item_row, rm_obj) + + def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0): + rm_obj.required_qty = required_qty + rm_obj.consumed_qty = consumed_qty + + def __set_batch_nos(self, bom_item, item_row, rm_obj, qty): + key = (rm_obj.rm_item_code, item_row.item_code, item_row.subcontracting_order) + + if self.available_materials.get(key) and self.available_materials[key]["batch_no"]: + new_rm_obj = None + for batch_no, batch_qty in self.available_materials[key]["batch_no"].items(): + if batch_qty >= qty: + self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty) + self.available_materials[key]["batch_no"][batch_no] -= qty + return + + elif qty > 0 and batch_qty > 0: + qty -= batch_qty + new_rm_obj = self.append(self.raw_material_table, bom_item) + new_rm_obj.reference_name = item_row.name + self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty) + self.available_materials[key]["batch_no"][batch_no] = 0 + + if abs(qty) > 0 and not new_rm_obj: + self.__set_consumed_qty(rm_obj, qty) + else: + self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty) + self.__set_serial_nos(item_row, rm_obj) + + def __add_supplied_item(self, item_row, bom_item, qty): + bom_item.conversion_factor = item_row.conversion_factor + rm_obj = self.append(self.raw_material_table, bom_item) + rm_obj.reference_name = item_row.name + + if self.doctype == "Subcontracting Receipt": + args = frappe._dict( + { + "item_code": rm_obj.rm_item_code, + "warehouse": self.supplier_warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "qty": -1 * flt(rm_obj.consumed_qty), + "serial_no": rm_obj.serial_no, + "batch_no": rm_obj.batch_no, + "voucher_type": self.doctype, + "voucher_no": self.name, + "company": self.company, + "allow_zero_valuation": 1, + } + ) + rm_obj.rate = get_incoming_rate(args) + + if self.doctype == "Subcontracting Order": + rm_obj.required_qty = qty + rm_obj.amount = rm_obj.required_qty * rm_obj.rate + else: + rm_obj.consumed_qty = 0 + rm_obj.subcontracting_order = item_row.subcontracting_order + self.__set_batch_nos(bom_item, item_row, rm_obj, qty) + + def __get_qty_based_on_material_transfer(self, item_row, transfer_item): + key = (item_row.item_code, item_row.subcontracting_order) + + if self.qty_to_be_received == item_row.qty: + return transfer_item.qty + + if self.qty_to_be_received: + qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0)) + transfer_item.item_details.required_qty = transfer_item.qty + + if transfer_item.serial_no or frappe.get_cached_value( + "UOM", transfer_item.item_details.stock_uom, "must_be_whole_number" + ): + return frappe.utils.ceil(qty) + + return qty + + def __set_supplied_items(self): + self.bom_items = {} + + has_supplied_items = True if self.get(self.raw_material_table) else False + for row in self.items: + if self.doctype != "Subcontracting Order" and ( + (self.__changed_name and row.name not in self.__changed_name) + or (has_supplied_items and not self.__changed_name) + ): + continue + + if self.doctype == "Subcontracting Order" or self.backflush_based_on == "BOM": + for bom_item in self.__get_materials_from_bom( + row.item_code, row.bom, row.get("include_exploded_items") + ): + qty = flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor + bom_item.main_item_code = row.item_code + self.__update_reserve_warehouse(bom_item, row) + self.__set_alternative_item(bom_item) + self.__add_supplied_item(row, bom_item, qty) + + elif self.backflush_based_on != "BOM": + for key, transfer_item in self.available_materials.items(): + if (key[1], key[2]) == (row.item_code, row.subcontracting_order) and transfer_item.qty > 0: + qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0 + transfer_item.qty -= qty + self.__add_supplied_item(row, transfer_item.get("item_details"), qty) + + if self.qty_to_be_received: + self.qty_to_be_received[(row.item_code, row.subcontracting_order)] -= row.qty + + def __prepare_supplied_items(self): + self.initialized_fields() + self.__get_subcontracting_orders() + self.__get_pending_qty_to_receive() + self.get_available_materials() + self.__remove_changed_rows() + self.__set_supplied_items() + + def __validate_batch_no(self, row, key): + if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get( + "batch_no" + ): + link = get_link_to_form("Subcontracting Order", row.subcontracting_order) + msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Subcontracting Order {link}' + frappe.throw(_(msg), title=_("Incorrect Batch Consumed")) + + def __validate_serial_no(self, row, key): + if row.get("serial_no"): + serial_nos = get_serial_nos(row.get("serial_no")) + incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get("serial_no")) + + if incorrect_sn: + incorrect_sn = "\n".join(incorrect_sn) + link = get_link_to_form("Subcontracting Order", row.subcontracting_order) + msg = f"The Serial Nos {incorrect_sn} has not supplied against the Subcontracting Order {link}" + frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed")) + + def __validate_supplied_items(self): + if self.doctype != "Subcontracting Receipt": + return + + for row in self.get(self.raw_material_table): + key = (row.rm_item_code, row.main_item_code, row.subcontracting_order) + if not self.__transferred_items or not self.__transferred_items.get(key): + return + + self.__validate_batch_no(row, key) + self.__validate_serial_no(row, key) + + def set_materials_for_subcontracted_items(self, raw_material_table): + self.raw_material_table = raw_material_table + self.__identify_change_in_item_table() + self.__prepare_supplied_items() + self.__validate_supplied_items() + + def create_raw_materials_supplied(self, raw_material_table="supplied_items"): + self.set_materials_for_subcontracted_items(raw_material_table) + + if self.doctype == "Subcontracting Receipt": + for item in self.get("items"): + item.rm_supp_cost = 0.0 + + def __update_consumed_qty_in_sco(self, itemwise_consumed_qty): + fields = ["main_item_code", "rm_item_code", "parent", "supplied_qty", "name"] + filters = {"docstatus": 1, "parent": ("in", self.subcontracting_orders)} + + for row in frappe.get_all( + "Subcontracting Order Supplied Item", fields=fields, filters=filters, order_by="idx" + ): + key = (row.rm_item_code, row.main_item_code, row.parent) + consumed_qty = itemwise_consumed_qty.get(key, 0) + + if row.supplied_qty < consumed_qty: + consumed_qty = row.supplied_qty + + itemwise_consumed_qty[key] -= consumed_qty + frappe.db.set_value( + "Subcontracting Order Supplied Item", row.name, "consumed_qty", consumed_qty + ) + + def set_consumed_qty_in_sco(self): + # Update consumed qty back in the subcontracting order + self.__get_subcontracting_orders() + itemwise_consumed_qty = defaultdict(float) + consumed_items, scr_items = self.__update_consumed_materials( + "Subcontracting Receipt", return_consumed_items=True + ) + + for row in consumed_items: + key = (row.rm_item_code, row.main_item_code, scr_items.get(row.reference_name)) + itemwise_consumed_qty[key] += row.consumed_qty + + self.__update_consumed_qty_in_sco(itemwise_consumed_qty) + + def update_ordered_and_reserved_qty(self): + sco_map = {} + for item in self.get("items"): + if self.doctype == "Subcontracting Receipt" and item.subcontracting_order: + sco_map.setdefault(item.subcontracting_order, []).append(item.subcontracting_order_item) + + for sco, sco_item_rows in sco_map.items(): + if sco and sco_item_rows: + sco_doc = frappe.get_doc("Subcontracting Order", sco) + + if sco_doc.status in ["Closed", "Cancelled"]: + frappe.throw( + _("{0} {1} is cancelled or closed").format(_("Subcontracting Order"), sco), + frappe.InvalidStatusError, + ) + + sco_doc.update_ordered_qty_for_subcontracting(sco_item_rows) + sco_doc.update_reserved_qty_for_subcontracting() + + def make_sl_entries_for_supplier_warehouse(self, sl_entries): + if hasattr(self, "supplied_items"): + for item in self.get("supplied_items"): + # negative quantity is passed, as raw material qty has to be decreased + # when SCR is submitted and it has to be increased when SCR is cancelled + sl_entries.append( + self.get_sl_entries( + item, + { + "item_code": item.rm_item_code, + "warehouse": self.supplier_warehouse, + "actual_qty": -1 * flt(item.consumed_qty), + "dependant_sle_voucher_detail_no": item.reference_name, + }, + ) + ) + + def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False): + self.update_ordered_and_reserved_qty() + + sl_entries = [] + stock_items = self.get_stock_items() + + for item in self.get("items"): + if item.item_code in stock_items and item.warehouse: + scr_qty = flt(item.qty) * flt(item.conversion_factor) + + if scr_qty: + sle = self.get_sl_entries( + item, {"actual_qty": flt(scr_qty), "serial_no": cstr(item.serial_no).strip()} + ) + rate_db_precision = 6 if cint(self.precision("rate", item)) <= 6 else 9 + incoming_rate = flt(item.rate, rate_db_precision) + sle.update( + { + "incoming_rate": incoming_rate, + "recalculate_rate": 1, + } + ) + sl_entries.append(sle) + + if flt(item.rejected_qty) != 0: + sl_entries.append( + self.get_sl_entries( + item, + { + "warehouse": item.rejected_warehouse, + "actual_qty": flt(item.rejected_qty) * flt(item.conversion_factor), + "serial_no": cstr(item.rejected_serial_no).strip(), + "incoming_rate": 0.0, + "recalculate_rate": 1, + }, + ) + ) + + self.make_sl_entries_for_supplier_warehouse(sl_entries) + self.make_sl_entries( + sl_entries, + allow_negative_stock=allow_negative_stock, + via_landed_cost_voucher=via_landed_cost_voucher, + ) + + def get_supplied_items_cost(self, item_row_id): + supplied_items_cost = 0.0 + for item in self.get("supplied_items"): + if item.reference_name == item_row_id: + item.amount = flt(flt(item.consumed_qty) * flt(item.rate), item.precision("amount")) + supplied_items_cost += item.amount + + return supplied_items_cost + + @property + def sub_contracted_items(self): + if not hasattr(self, "_sub_contracted_items"): + self._sub_contracted_items = [] + item_codes = list(set(item.item_code for item in self.get("items"))) + if item_codes: + items = frappe.get_all( + "Item", filters={"name": ["in", item_codes], "is_sub_contracted_item": 1} + ) + self._sub_contracted_items = [item.name for item in items] + + return self._sub_contracted_items From dcac7eb67c50d57ca639eee36c1b9840b9c1b6d5 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 1 Apr 2022 19:25:44 +0530 Subject: [PATCH 07/98] feat: New DocType "Subcontracting Order Service Item" --- erpnext/subcontracting/doctype/__init__.py | 0 .../__init__.py | 0 .../subcontracting_order_service_item.json | 131 ++++++++++++++++++ .../subcontracting_order_service_item.py | 9 ++ 4 files changed, 140 insertions(+) create mode 100644 erpnext/subcontracting/doctype/__init__.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_order_service_item/__init__.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json create mode 100644 erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.py diff --git a/erpnext/subcontracting/doctype/__init__.py b/erpnext/subcontracting/doctype/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/subcontracting/doctype/subcontracting_order_service_item/__init__.py b/erpnext/subcontracting/doctype/subcontracting_order_service_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json new file mode 100644 index 0000000000..f213313ef6 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json @@ -0,0 +1,131 @@ +{ + "actions": [], + "autoname": "hash", + "creation": "2022-04-01 19:23:05.728354", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "column_break_2", + "item_name", + "section_break_4", + "qty", + "column_break_6", + "rate", + "column_break_8", + "amount", + "section_break_10", + "fg_item", + "column_break_12", + "fg_item_qty" + ], + "fields": [ + { + "bold": 1, + "columns": 2, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1, + "search_index": 1 + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Item Name", + "print_hide": 1, + "reqd": 1 + }, + { + "bold": 1, + "columns": 1, + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Quantity", + "print_width": "60px", + "reqd": 1, + "width": "60px" + }, + { + "bold": 1, + "columns": 2, + "fetch_from": "item_code.standard_rate", + "fetch_if_empty": 1, + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "options": "currency", + "reqd": 1 + }, + { + "columns": 2, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "fg_item", + "fieldtype": "Link", + "label": "Finished Good Item", + "options": "Item", + "reqd": 1 + }, + { + "default": "1", + "fieldname": "fg_item_qty", + "fieldtype": "Float", + "label": "Finished Good Item Quantity", + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + } + ], + "istable": 1, + "links": [], + "modified": "2022-04-07 11:43:43.094867", + "modified_by": "Administrator", + "module": "Subcontracting", + "name": "Subcontracting Order Service Item", + "naming_rule": "Random", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "search_fields": "item_name", + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.py b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.py new file mode 100644 index 0000000000..ad6289d923 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class SubcontractingOrderServiceItem(Document): + pass From f8b759429232df4da6a9bd577e8ac3623fb1db41 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 1 Apr 2022 19:28:25 +0530 Subject: [PATCH 08/98] feat: New DocType "Subcontracting Order Item" --- .../subcontracting_order_item/__init__.py | 0 .../subcontracting_order_item.json | 326 ++++++++++++++++++ .../subcontracting_order_item.py | 9 + 3 files changed, 335 insertions(+) create mode 100644 erpnext/subcontracting/doctype/subcontracting_order_item/__init__.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json create mode 100644 erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/__init__.py b/erpnext/subcontracting/doctype/subcontracting_order_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json new file mode 100644 index 0000000000..291f47a634 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json @@ -0,0 +1,326 @@ +{ + "actions": [], + "autoname": "hash", + "creation": "2022-04-01 19:26:31.475015", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "bom", + "include_exploded_items", + "column_break_3", + "schedule_date", + "expected_delivery_date", + "description_section", + "description", + "column_break_8", + "image", + "image_view", + "quantity_and_rate_section", + "qty", + "received_qty", + "returned_qty", + "column_break_13", + "stock_uom", + "conversion_factor", + "section_break_16", + "rate", + "amount", + "column_break_19", + "rm_cost_per_qty", + "service_cost_per_qty", + "additional_cost_per_qty", + "warehouse_section", + "warehouse", + "accounting_details_section", + "expense_account", + "manufacture_section", + "manufacturer", + "manufacturer_part_no", + "section_break_34", + "page_break" + ], + "fields": [ + { + "bold": 1, + "columns": 2, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "read_only": 1, + "reqd": 1, + "search_index": 1 + }, + { + "fetch_from": "item_code.item_name", + "fetch_if_empty": 1, + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Item Name", + "print_hide": 1, + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "bold": 1, + "columns": 2, + "fieldname": "schedule_date", + "fieldtype": "Date", + "label": "Required By", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "bold": 1, + "fieldname": "expected_delivery_date", + "fieldtype": "Date", + "label": "Expected Delivery Date", + "search_index": 1 + }, + { + "collapsible": 1, + "fieldname": "description_section", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "fetch_from": "item_code.description", + "fetch_if_empty": 1, + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description", + "print_width": "300px", + "reqd": 1, + "width": "300px" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "image", + "fieldtype": "Attach", + "hidden": 1, + "label": "Image" + }, + { + "fieldname": "image_view", + "fieldtype": "Image", + "label": "Image View", + "options": "image", + "print_hide": 1 + }, + { + "fieldname": "quantity_and_rate_section", + "fieldtype": "Section Break", + "label": "Quantity and Rate" + }, + { + "bold": 1, + "columns": 1, + "default": "1", + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Quantity", + "print_width": "60px", + "read_only": 1, + "reqd": 1, + "width": "60px" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break", + "print_hide": 1 + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "print_width": "100px", + "read_only": 1, + "reqd": 1, + "width": "100px" + }, + { + "default": "1", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "hidden": 1, + "label": "Conversion Factor", + "read_only": 1 + }, + { + "fieldname": "section_break_16", + "fieldtype": "Section Break" + }, + { + "bold": 1, + "columns": 2, + "fetch_from": "item_code.standard_rate", + "fetch_if_empty": 1, + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "options": "currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, + { + "columns": 2, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "warehouse_section", + "fieldtype": "Section Break", + "label": "Warehouse Details" + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "options": "Warehouse", + "print_hide": 1, + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_details_section", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "expense_account", + "fieldtype": "Link", + "label": "Expense Account", + "options": "Account", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "manufacture_section", + "fieldtype": "Section Break", + "label": "Manufacture" + }, + { + "fieldname": "manufacturer", + "fieldtype": "Link", + "label": "Manufacturer", + "options": "Manufacturer" + }, + { + "fieldname": "manufacturer_part_no", + "fieldtype": "Data", + "label": "Manufacturer Part Number" + }, + { + "depends_on": "item_code", + "fetch_from": "item_code.default_bom", + "fieldname": "bom", + "fieldtype": "Link", + "in_list_view": 1, + "label": "BOM", + "options": "BOM", + "print_hide": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "include_exploded_items", + "fieldtype": "Check", + "label": "Include Exploded Items", + "print_hide": 1 + }, + { + "fieldname": "service_cost_per_qty", + "fieldtype": "Currency", + "label": "Service Cost Per Qty", + "read_only": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "additional_cost_per_qty", + "fieldtype": "Currency", + "label": "Additional Cost Per Qty", + "read_only": 1 + }, + { + "fieldname": "rm_cost_per_qty", + "fieldtype": "Currency", + "label": "Raw Material Cost Per Qty", + "no_copy": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "page_break", + "fieldtype": "Check", + "label": "Page Break", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "section_break_34", + "fieldtype": "Section Break" + }, + { + "depends_on": "received_qty", + "fieldname": "received_qty", + "fieldtype": "Float", + "label": "Received Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "returned_qty", + "fieldname": "returned_qty", + "fieldtype": "Float", + "label": "Returned Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + } + ], + "idx": 1, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2022-04-11 21:28:06.585338", + "modified_by": "Administrator", + "module": "Subcontracting", + "name": "Subcontracting Order Item", + "naming_rule": "Random", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "search_fields": "item_name", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py new file mode 100644 index 0000000000..174f5b212c --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class SubcontractingOrderItem(Document): + pass From f49c51ab7439f668b1a25049e88394a4c8649912 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 1 Apr 2022 19:31:10 +0530 Subject: [PATCH 09/98] feat: New DocType "Subcontracting Order Supplied Item" --- .../__init__.py | 0 .../subcontracting_order_supplied_item.json | 178 ++++++++++++++++++ .../subcontracting_order_supplied_item.py | 9 + 3 files changed, 187 insertions(+) create mode 100644 erpnext/subcontracting/doctype/subcontracting_order_supplied_item/__init__.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json create mode 100644 erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.py diff --git a/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/__init__.py b/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json b/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json new file mode 100644 index 0000000000..a206a21ca6 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json @@ -0,0 +1,178 @@ +{ + "actions": [], + "creation": "2022-04-01 19:29:30.923800", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "main_item_code", + "rm_item_code", + "column_break_3", + "stock_uom", + "conversion_factor", + "reserve_warehouse", + "column_break_6", + "bom_detail_no", + "reference_name", + "section_break_9", + "rate", + "column_break_11", + "amount", + "section_break_13", + "required_qty", + "supplied_qty", + "column_break_16", + "consumed_qty", + "returned_qty", + "total_supplied_qty" + ], + "fields": [ + { + "columns": 2, + "fieldname": "main_item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "read_only": 1 + }, + { + "columns": 2, + "fieldname": "rm_item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Raw Material Item Code", + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock Uom", + "options": "UOM", + "read_only": 1 + }, + { + "default": "1", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "hidden": 1, + "label": "Conversion Factor", + "read_only": 1 + }, + { + "columns": 2, + "fieldname": "reserve_warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Reserve Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "bom_detail_no", + "fieldtype": "Data", + "label": "BOM Detail No", + "read_only": 1 + }, + { + "fieldname": "reference_name", + "fieldtype": "Data", + "label": "Reference Name", + "read_only": 1 + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, + { + "columns": 2, + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "options": "Company:company:default_currency" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "section_break_13", + "fieldtype": "Section Break" + }, + { + "columns": 2, + "fieldname": "required_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Required Qty", + "read_only": 1 + }, + { + "fieldname": "supplied_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Supplied Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "fieldname": "consumed_qty", + "fieldtype": "Float", + "label": "Consumed Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "returned_qty", + "fieldtype": "Float", + "label": "Returned Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "hidden": 1 + }, + { + "fieldname": "total_supplied_qty", + "fieldtype": "Float", + "hidden": 1, + "label": "Total Supplied Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + } + ], + "hide_toolbar": 1, + "istable": 1, + "links": [], + "modified": "2022-04-07 12:58:28.208847", + "modified_by": "Administrator", + "module": "Subcontracting", + "name": "Subcontracting Order Supplied Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.py b/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.py new file mode 100644 index 0000000000..5619e3b79a --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class SubcontractingOrderSuppliedItem(Document): + pass From 249726b845522f9b7858ca2ed1845e284c7a91f9 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 22 Apr 2022 17:09:19 +0530 Subject: [PATCH 10/98] feat: New DocType "Subcontracting Order" --- .../controllers/subcontracting_controller.py | 11 + .../stock/doctype/stock_entry/stock_entry.py | 10 + .../doctype/subcontracting_order/__init__.py | 0 .../subcontracting_order.js | 322 ++++++++++++ .../subcontracting_order.json | 485 ++++++++++++++++++ .../subcontracting_order.py | 372 ++++++++++++++ .../subcontracting_order_dashboard.py | 8 + .../subcontracting_order_list.js | 16 + .../test_subcontracting_order.py | 8 + 9 files changed, 1232 insertions(+) create mode 100644 erpnext/subcontracting/doctype/subcontracting_order/__init__.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js create mode 100644 erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json create mode 100644 erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_dashboard.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js create mode 100644 erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index b393737740..4e0d91147e 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -627,6 +627,17 @@ class SubcontractingController(StockController): return supplied_items_cost + def set_subcontracting_order_status(self): + if self.doctype == "Subcontracting Order": + self.update_status() + elif self.doctype == "Subcontracting Receipt": + self.__get_subcontracting_orders + + if self.subcontracting_orders: + for sco in set(self.subcontracting_orders): + sco_doc = frappe.get_doc("Subcontracting Order", sco) + sco_doc.update_status() + @property def sub_contracted_items(self): if not hasattr(self, "_sub_contracted_items"): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index b851795389..5adb8b273e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -136,6 +136,7 @@ class StockEntry(StockController): self.update_work_order() self.validate_subcontracting_order() self.update_subcontracting_order_supplied_items() + self.update_subcontracting_order_status() self.make_gl_entries() @@ -155,6 +156,7 @@ class StockEntry(StockController): def on_cancel(self): self.update_subcontracting_order_supplied_items() + self.update_subcontracting_order_status() if self.work_order and self.purpose == "Material Consumption for Manufacture": self.validate_work_order_status() @@ -2212,6 +2214,14 @@ class StockEntry(StockController): return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos))) + def update_subcontracting_order_status(self): + if self.subcontracting_order and self.purpose == "Send to Subcontractor": + from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + update_subcontracting_order_status, + ) + + update_subcontracting_order_status(self.subcontracting_order) + @frappe.whitelist() def move_sample_to_retention_warehouse(company, items): diff --git a/erpnext/subcontracting/doctype/subcontracting_order/__init__.py b/erpnext/subcontracting/doctype/subcontracting_order/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js new file mode 100644 index 0000000000..80fe94483b --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -0,0 +1,322 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.provide('erpnext.buying'); + +frappe.ui.form.on('Subcontracting Order', { + setup: (frm) => { + frm.get_field("items").grid.cannot_add_rows = true; + frm.get_field("items").grid.only_sortable(); + + frm.set_indicator_formatter('item_code', + (doc) => (doc.qty <= doc.received_qty) ? 'green' : 'orange'); + + frm.set_query('supplier_warehouse', () => { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); + + frm.set_query('purchase_order', () => { + return { + filters: { + docstatus: 1, + is_subcontracted: "Yes" + } + }; + }); + + frm.set_query('set_warehouse', () => { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); + + frm.set_query('warehouse', 'items', () => ({ + filters: { + company: frm.doc.company, + is_group: 0 + } + })); + + frm.set_query('expense_account', 'items', () => ({ + query: 'erpnext.controllers.queries.get_expense_account', + filters: { + company: frm.doc.company + } + })); + + frm.set_query('bom', 'items', (doc, cdt, cdn) => { + let d = locals[cdt][cdn]; + return { + filters: { + item: d.item_code, + is_active: 1, + docstatus: 1, + company: frm.doc.company + } + }; + }); + + frm.set_query('set_reserve_warehouse', () => { + return { + filters: { + company: frm.doc.company, + name: ['!=', frm.doc.supplier_warehouse], + is_group: 0 + } + }; + }); + }, + + onload: (frm) => { + if (!frm.doc.transaction_date) { + frm.set_value('transaction_date', frappe.datetime.get_today()); + } + }, + + purchase_order: (frm) => { + frm.set_value('service_items', null); + frm.set_value('items', null); + frm.set_value('supplied_items', null); + + if (frm.doc.purchase_order) { + erpnext.utils.map_current_doc({ + method: 'erpnext.buying.doctype.purchase_order.purchase_order.make_subcontracting_order', + source_name: frm.doc.purchase_order, + target_doc: frm, + freeze: true, + freeze_message: __('Mapping Subcontracting Order ...'), + }); + } + }, + + refresh: function (frm) { + frm.trigger('get_materials_from_supplier'); + }, + + get_materials_from_supplier: function (frm) { + let sco_rm_details = []; + + if (frm.doc.supplied_items && (frm.doc.per_received == 100)) { + frm.doc.supplied_items.forEach(d => { + if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) { + sco_rm_details.push(d.name); + } + }); + } + + if (sco_rm_details && sco_rm_details.length) { + frm.add_custom_button(__('Return of Components'), () => { + frm.call({ + method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.get_materials_from_supplier', + freeze: true, + freeze_message: __('Creating Stock Entry'), + args: { subcontracting_order: frm.doc.name, sco_rm_details: sco_rm_details }, + callback: function (r) { + if (r && r.message) { + const doc = frappe.model.sync(r.message); + frappe.set_route("Form", doc[0].doctype, doc[0].name); + } + } + }); + }, __('Create')); + } + } +}); + +erpnext.buying.SubcontractingOrderController = class SubcontractingOrderController { + setup() { + this.frm.custom_make_buttons = { + 'Subcontracting Receipt': 'Subcontracting Receipt', + 'Stock Entry': 'Material to Supplier', + }; + } + + refresh(doc) { + var me = this; + + if (doc.docstatus == 1) { + if (doc.status != 'Completed') { + if (flt(doc.per_received) < 100) { + cur_frm.add_custom_button(__('Subcontracting Receipt'), this.make_subcontracting_receipt, __('Create')); + if (me.has_unsupplied_items()) { + cur_frm.add_custom_button(__('Material to Supplier'), + () => { + me.make_stock_entry(); + }, __('Transfer')); + } + } + cur_frm.page.set_inner_btn_group_as_primary(__('Create')); + } + } + } + + items_add(doc, cdt, cdn) { + if (doc.set_warehouse) { + var row = frappe.get_doc(cdt, cdn); + row.warehouse = doc.set_warehouse; + } + } + + set_warehouse(doc) { + this.set_warehouse_in_children(doc.items, "warehouse", doc.set_warehouse); + } + + set_reserve_warehouse(doc) { + this.set_warehouse_in_children(doc.supplied_items, "reserve_warehouse", doc.set_reserve_warehouse); + } + + set_warehouse_in_children(child_table, warehouse_field, warehouse) { + let transaction_controller = new erpnext.TransactionController(); + transaction_controller.autofill_warehouse(child_table, warehouse_field, warehouse); + } + + make_stock_entry() { + var items = $.map(cur_frm.doc.items, (d) => d.bom ? d.item_code : false); + var me = this; + + if (items.length >= 1) { + me.raw_material_data = []; + me.show_dialog = 1; + let title = __('Transfer Material to Supplier'); + let fields = [ + { fieldtype: 'Section Break', label: __('Raw Materials') }, + { + fieldname: 'sub_con_rm_items', fieldtype: 'Table', label: __('Items'), + fields: [ + { + fieldtype: 'Data', + fieldname: 'item_code', + label: __('Item'), + read_only: 1, + in_list_view: 1 + }, + { + fieldtype: 'Data', + fieldname: 'rm_item_code', + label: __('Raw Material'), + read_only: 1, + in_list_view: 1 + }, + { + fieldtype: 'Float', + read_only: 1, + fieldname: 'qty', + label: __('Quantity'), + in_list_view: 1 + }, + { + fieldtype: 'Data', + read_only: 1, + fieldname: 'warehouse', + label: __('Reserve Warehouse'), + in_list_view: 1 + }, + { + fieldtype: 'Float', + read_only: 1, + fieldname: 'rate', + label: __('Rate'), + hidden: 1 + }, + { + fieldtype: 'Float', + read_only: 1, + fieldname: 'amount', + label: __('Amount'), + hidden: 1 + }, + { + fieldtype: 'Link', + read_only: 1, + fieldname: 'uom', + label: __('UOM'), + hidden: 1 + } + ], + data: me.raw_material_data, + get_data: () => me.raw_material_data + } + ]; + + me.dialog = new frappe.ui.Dialog({ + title: title, fields: fields + }); + + if (me.frm.doc['supplied_items']) { + me.frm.doc['supplied_items'].forEach((item) => { + if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) { + me.raw_material_data.push({ + 'name': item.name, + 'item_code': item.main_item_code, + 'rm_item_code': item.rm_item_code, + 'item_name': item.rm_item_code, + 'qty': item.required_qty - item.supplied_qty, + 'warehouse': item.reserve_warehouse, + 'rate': item.rate, + 'amount': item.amount, + 'stock_uom': item.stock_uom + }); + me.dialog.fields_dict.sub_con_rm_items.grid.refresh(); + } + }); + } + + me.dialog.get_field('sub_con_rm_items').check_all_rows(); + + me.dialog.show(); + this.dialog.set_primary_action(__('Transfer'), () => { + me.values = me.dialog.get_values(); + if (me.values) { + me.values.sub_con_rm_items.map((row, i) => { + if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) { + let row_id = i + 1; + frappe.throw(__('Item Code, warehouse and quantity are required on row {0}', [row_id])); + } + }); + me.make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children()); + me.dialog.hide(); + } + }); + } + + me.dialog.get_close_btn().on('click', () => { + me.dialog.hide(); + }); + } + + has_unsupplied_items() { + return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty); + } + + make_subcontracting_receipt() { + frappe.model.open_mapped_doc({ + method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_subcontracting_receipt', + frm: cur_frm, + freeze_message: __('Creating Subcontracting Receipt ...') + }); + } + + make_rm_stock_entry(rm_items) { + frappe.call({ + method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_rm_stock_entry', + args: { + subcontracting_order: cur_frm.doc.name, + rm_items: rm_items + }, + callback: (r) => { + var doclist = frappe.model.sync(r.message); + frappe.set_route('Form', doclist[0].doctype, doclist[0].name); + } + }); + } +}; + +extend_cscript(cur_frm.cscript, new erpnext.buying.SubcontractingOrderController({ frm: cur_frm })); \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json new file mode 100644 index 0000000000..c6e76c76d7 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json @@ -0,0 +1,485 @@ +{ + "actions": [], + "allow_auto_repeat": 1, + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2022-04-01 22:39:17.662819", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "title", + "naming_series", + "purchase_order", + "supplier", + "supplier_name", + "supplier_warehouse", + "column_break_7", + "company", + "transaction_date", + "schedule_date", + "amended_from", + "address_and_contact_section", + "supplier_address", + "address_display", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "column_break_19", + "shipping_address", + "shipping_address_display", + "billing_address", + "billing_address_display", + "section_break_24", + "column_break_25", + "set_warehouse", + "items", + "section_break_32", + "total_qty", + "column_break_29", + "total", + "service_items_section", + "service_items", + "raw_materials_supplied_section", + "set_reserve_warehouse", + "supplied_items", + "additional_costs_section", + "distribute_additional_costs_based_on", + "additional_costs", + "total_additional_costs", + "order_status_section", + "status", + "column_break_39", + "per_received", + "printing_settings_section", + "select_print_heading", + "column_break_43", + "letter_head" + ], + "fields": [ + { + "allow_on_submit": 1, + "default": "{supplier_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "SC-ORD-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "purchase_order", + "fieldtype": "Link", + "label": "Subcontracting Purchase Order", + "options": "Purchase Order", + "reqd": 1 + }, + { + "bold": 1, + "fieldname": "supplier", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Supplier", + "options": "Supplier", + "print_hide": 1, + "reqd": 1, + "search_index": 1 + }, + { + "bold": 1, + "fetch_from": "supplier.supplier_name", + "fieldname": "supplier_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Supplier Name", + "read_only": 1, + "reqd": 1 + }, + { + "depends_on": "supplier", + "fieldname": "supplier_warehouse", + "fieldtype": "Link", + "label": "Supplier Warehouse", + "options": "Warehouse", + "reqd": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break", + "print_width": "50%", + "width": "50%" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "print_hide": 1, + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "default": "Today", + "fetch_from": "purchase_order.transaction_date", + "fetch_if_empty": 1, + "fieldname": "transaction_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "reqd": 1, + "search_index": 1 + }, + { + "allow_on_submit": 1, + "fetch_from": "purchase_order.schedule_date", + "fetch_if_empty": 1, + "fieldname": "schedule_date", + "fieldtype": "Date", + "label": "Required By", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "options": "Subcontracting Order", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "address_and_contact_section", + "fieldtype": "Section Break", + "label": "Address and Contact" + }, + { + "fetch_from": "supplier.supplier_primary_address", + "fetch_if_empty": 1, + "fieldname": "supplier_address", + "fieldtype": "Link", + "label": "Supplier Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "address_display", + "fieldtype": "Small Text", + "label": "Supplier Address Details", + "read_only": 1 + }, + { + "fetch_from": "supplier.supplier_primary_contact", + "fetch_if_empty": 1, + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Supplier Contact", + "options": "Contact", + "print_hide": 1 + }, + { + "fieldname": "contact_display", + "fieldtype": "Small Text", + "in_global_search": 1, + "label": "Contact Name", + "read_only": 1 + }, + { + "fieldname": "contact_mobile", + "fieldtype": "Small Text", + "label": "Contact Mobile No", + "read_only": 1 + }, + { + "fieldname": "contact_email", + "fieldtype": "Small Text", + "label": "Contact Email", + "options": "Email", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_address", + "fieldtype": "Link", + "label": "Company Shipping Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "shipping_address_display", + "fieldtype": "Small Text", + "label": "Shipping Address Details", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "billing_address", + "fieldtype": "Link", + "label": "Company Billing Address", + "options": "Address" + }, + { + "fieldname": "billing_address_display", + "fieldtype": "Small Text", + "label": "Billing Address Details", + "read_only": 1 + }, + { + "fieldname": "section_break_24", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break" + }, + { + "depends_on": "purchase_order", + "description": "Sets 'Warehouse' in each row of the Items table.", + "fieldname": "set_warehouse", + "fieldtype": "Link", + "label": "Set Target Warehouse", + "options": "Warehouse", + "print_hide": 1 + }, + { + "allow_bulk_edit": 1, + "depends_on": "purchase_order", + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "options": "Subcontracting Order Item", + "reqd": 1 + }, + { + "fieldname": "section_break_32", + "fieldtype": "Section Break" + }, + { + "depends_on": "purchase_order", + "fieldname": "total_qty", + "fieldtype": "Float", + "label": "Total Quantity", + "read_only": 1 + }, + { + "fieldname": "column_break_29", + "fieldtype": "Column Break" + }, + { + "depends_on": "purchase_order", + "fieldname": "total", + "fieldtype": "Currency", + "label": "Total", + "options": "currency", + "read_only": 1 + }, + { + "collapsible": 1, + "depends_on": "purchase_order", + "fieldname": "service_items_section", + "fieldtype": "Section Break", + "label": "Service Items" + }, + { + "fieldname": "service_items", + "fieldtype": "Table", + "label": "Service Items", + "options": "Subcontracting Order Service Item", + "read_only": 1, + "reqd": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "supplied_items", + "depends_on": "supplied_items", + "fieldname": "raw_materials_supplied_section", + "fieldtype": "Section Break", + "label": "Raw Materials Supplied" + }, + { + "depends_on": "supplied_items", + "description": "Sets 'Reserve Warehouse' in each row of the Supplied Items table.", + "fieldname": "set_reserve_warehouse", + "fieldtype": "Link", + "label": "Set Reserve Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "supplied_items", + "fieldtype": "Table", + "label": "Supplied Items", + "no_copy": 1, + "options": "Subcontracting Order Supplied Item", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "total_additional_costs", + "depends_on": "eval:(doc.docstatus == 0 || doc.total_additional_costs)", + "fieldname": "additional_costs_section", + "fieldtype": "Section Break", + "label": "Additional Costs" + }, + { + "fieldname": "additional_costs", + "fieldtype": "Table", + "label": "Additional Costs", + "options": "Landed Cost Taxes and Charges" + }, + { + "fieldname": "total_additional_costs", + "fieldtype": "Currency", + "label": "Total Additional Costs", + "print_hide_if_no_value": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "order_status_section", + "fieldtype": "Section Break", + "label": "Order Status" + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "Draft\nOpen\nPartially Received\nCompleted\nMaterial Transferred\nPartial Material Transferred\nCancelled", + "print_hide": 1, + "read_only": 1, + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "column_break_39", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "per_received", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "% Received", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "printing_settings_section", + "fieldtype": "Section Break", + "label": "Printing Settings", + "print_hide": 1, + "print_width": "50%", + "width": "50%" + }, + { + "allow_on_submit": 1, + "fieldname": "select_print_heading", + "fieldtype": "Link", + "label": "Print Heading", + "no_copy": 1, + "options": "Print Heading", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "column_break_43", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "options": "Letter Head", + "print_hide": 1 + }, + { + "default": "Qty", + "fieldname": "distribute_additional_costs_based_on", + "fieldtype": "Select", + "label": "Distribute Additional Costs Based On ", + "options": "Qty\nAmount" + } + ], + "icon": "fa fa-file-text", + "is_submittable": 1, + "links": [], + "modified": "2022-04-11 21:02:44.097841", + "modified_by": "Administrator", + "module": "Subcontracting", + "name": "Subcontracting Order", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "read": 1, + "report": 1, + "role": "Stock User" + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "role": "Purchase Manager", + "write": 1 + } + ], + "search_fields": "status, transaction_date, supplier", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "timeline_field": "supplier", + "title_field": "supplier_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py new file mode 100644 index 0000000000..d12c9e825c --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -0,0 +1,372 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import json + +import frappe +from frappe import _ +from frappe.model.mapper import get_mapped_doc +from frappe.utils import flt + +from erpnext.controllers.subcontracting_controller import SubcontractingController +from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty +from erpnext.stock.utils import get_bin + + +class SubcontractingOrder(SubcontractingController): + def before_validate(self): + super(SubcontractingOrder, self).before_validate() + + def validate(self): + super(SubcontractingOrder, self).validate() + self.validate_purchase_order_for_subcontracting() + self.validate_items() + self.validate_service_items() + self.validate_supplied_items() + self.set_missing_values() + self.reset_default_field_value("set_warehouse", "items", "warehouse") + + def on_submit(self): + self.update_ordered_qty_for_subcontracting() + self.update_reserved_qty_for_subcontracting() + self.update_status() + + def on_cancel(self): + self.update_ordered_qty_for_subcontracting() + self.update_reserved_qty_for_subcontracting() + self.update_status() + + def validate_purchase_order_for_subcontracting(self): + if self.purchase_order: + po = frappe.get_doc("Purchase Order", self.purchase_order) + if not po.is_subcontracted: + frappe.throw(_("Please select a valid Purchase Order that is configured for Subcontracting.")) + + if po.docstatus != 1: + msg = f"Please submit Purchase Order {po.name} before proceeding." + frappe.throw(_(msg)) + + if po.per_received == 100: + msg = f"Cannot create more Subcontracting Orders against the Purchase Order {po.name}." + frappe.throw(_(msg)) + else: + self.service_items = self.items = self.supplied_items = None + frappe.throw(_("Please select a Subcontracting Purchase Order.")) + + def validate_service_items(self): + for item in self.service_items: + if frappe.get_value("Item", item.item_code, "is_stock_item"): + msg = f"Service Item {item.item_name} must be a non-stock item." + frappe.throw(_(msg)) + + def validate_supplied_items(self): + if self.supplier_warehouse: + for item in self.supplied_items: + if self.supplier_warehouse == item.reserve_warehouse: + msg = f"Reserve Warehouse must be different from Supplier Warehouse for Supplied Item {item.main_item_code}." + frappe.throw(_(msg)) + + def set_missing_values(self): + self.set_missing_values_in_additional_costs() + self.set_missing_values_in_service_items() + self.set_missing_values_in_supplied_items() + self.set_missing_values_in_items() + + def set_missing_values_in_additional_costs(self): + if self.get("additional_costs"): + self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs")) + + if self.total_additional_costs: + if self.distribute_additional_costs_based_on == "Amount": + total_amt = sum(flt(item.amount) for item in self.get("items")) + for item in self.items: + item.additional_cost_per_qty = ( + (item.amount * self.total_additional_costs) / total_amt + ) / item.qty + else: + total_qty = sum(flt(item.qty) for item in self.get("items")) + additional_cost_per_qty = self.total_additional_costs / total_qty + for item in self.items: + item.additional_cost_per_qty = additional_cost_per_qty + else: + self.total_additional_costs = 0 + + def set_missing_values_in_service_items(self): + for idx, item in enumerate(self.get("service_items")): + self.items[idx].service_cost_per_qty = item.amount / self.items[idx].qty + + def set_missing_values_in_supplied_items(self): + for item in self.get("items"): + bom = frappe.get_doc("BOM", item.bom) + rm_cost = sum(flt(rm_item.amount) for rm_item in bom.items) + item.rm_cost_per_qty = rm_cost / flt(bom.quantity) + + def set_missing_values_in_items(self): + total_qty = total = 0 + for item in self.items: + item.rate = ( + item.rm_cost_per_qty + item.service_cost_per_qty + (item.additional_cost_per_qty or 0) + ) + item.amount = item.qty * item.rate + total_qty += flt(item.qty) + total += flt(item.amount) + else: + self.total_qty = total_qty + self.total = total + + def update_ordered_qty_for_subcontracting(self, sco_item_rows=None): + item_wh_list = [] + for item in self.get("items"): + if ( + (not sco_item_rows or item.name in sco_item_rows) + and [item.item_code, item.warehouse] not in item_wh_list + and frappe.get_cached_value("Item", item.item_code, "is_stock_item") + and item.warehouse + ): + item_wh_list.append([item.item_code, item.warehouse]) + for item_code, warehouse in item_wh_list: + update_bin_qty(item_code, warehouse, {"ordered_qty": get_ordered_qty(item_code, warehouse)}) + + def update_reserved_qty_for_subcontracting(self): + for item in self.supplied_items: + if item.rm_item_code: + stock_bin = get_bin(item.rm_item_code, item.reserve_warehouse) + stock_bin.update_reserved_qty_for_sub_contracting() + + def populate_items_table(self): + items = [] + + for si in self.service_items: + if si.fg_item: + item = frappe.get_doc("Item", si.fg_item) + bom = frappe.db.get_value("BOM", {"item": item.item_code, "is_active": 1, "is_default": 1}) + + items.append( + { + "item_code": item.item_code, + "item_name": item.item_name, + "schedule_date": self.schedule_date, + "description": item.description, + "qty": si.fg_item_qty, + "stock_uom": item.stock_uom, + "bom": bom, + }, + ) + else: + frappe.throw( + _("Please select Finished Good Item for Service Item {0}").format( + si.item_name or si.item_code + ) + ) + else: + for item in items: + self.append("items", item) + else: + self.set_missing_values() + + def update_status(self, status=None, update_modified=False): + if self.docstatus >= 1 and not status: + if self.docstatus == 1: + if self.status == "Draft": + status = "Open" + elif self.per_received >= 100: + status = "Completed" + elif self.per_received > 0 and self.per_received < 100: + status = "Partially Received" + else: + total_required_qty = total_supplied_qty = 0 + for item in self.supplied_items: + total_required_qty += item.required_qty + total_supplied_qty += item.supplied_qty or 0 + if total_supplied_qty: + status = "Partial Material Transferred" + if total_supplied_qty >= total_required_qty: + status = "Material Transferred" + elif self.docstatus == 2: + status = "Cancelled" + + if status: + frappe.db.set_value("Subcontracting Order", self.name, "status", status, update_modified) + + +@frappe.whitelist() +def make_subcontracting_receipt(source_name, target_doc=None): + return get_mapped_subcontracting_receipt(source_name, target_doc) + + +def get_mapped_subcontracting_receipt(source_name, target_doc=None): + def update_item(obj, target, source_parent): + target.qty = flt(obj.qty) - flt(obj.received_qty) + target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) + + target_doc = get_mapped_doc( + "Subcontracting Order", + source_name, + { + "Subcontracting Order": { + "doctype": "Subcontracting Receipt", + "field_map": {"supplier_warehouse": "supplier_warehouse"}, + "validation": { + "docstatus": ["=", 1], + }, + }, + "Subcontracting Order Item": { + "doctype": "Subcontracting Receipt Item", + "field_map": { + "name": "subcontracting_order_item", + "parent": "subcontracting_order", + "bom": "bom", + }, + "postprocess": update_item, + "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty), + }, + }, + target_doc, + ) + + return target_doc + + +def get_item_details(items): + item = frappe.qb.DocType("Item") + item_list = ( + frappe.qb.from_(item) + .select(item.item_code, item.description, item.allow_alternative_item) + .where(item.name.isin(items)) + .run(as_dict=True) + ) + + item_details = {} + for item in item_list: + item_details[item.item_code] = item + + return item_details + + +@frappe.whitelist() +def make_rm_stock_entry(subcontracting_order, rm_items): + rm_items_list = rm_items + + if isinstance(rm_items, str): + rm_items_list = json.loads(rm_items) + elif not rm_items: + frappe.throw(_("No Items available for transfer")) + + if rm_items_list: + fg_items = list(set(item["item_code"] for item in rm_items_list)) + else: + frappe.throw(_("No Items selected for transfer")) + + if subcontracting_order: + subcontracting_order = frappe.get_doc("Subcontracting Order", subcontracting_order) + + if fg_items: + items = tuple(set(item["rm_item_code"] for item in rm_items_list)) + item_wh = get_item_details(items) + + stock_entry = frappe.new_doc("Stock Entry") + stock_entry.purpose = "Send to Subcontractor" + stock_entry.subcontracting_order = subcontracting_order.name + stock_entry.supplier = subcontracting_order.supplier + stock_entry.supplier_name = subcontracting_order.supplier_name + stock_entry.supplier_address = subcontracting_order.supplier_address + stock_entry.address_display = subcontracting_order.address_display + stock_entry.company = subcontracting_order.company + stock_entry.to_warehouse = subcontracting_order.supplier_warehouse + stock_entry.set_stock_entry_type() + + for item_code in fg_items: + for rm_item_data in rm_items_list: + if rm_item_data["item_code"] == item_code: + rm_item_code = rm_item_data["rm_item_code"] + items_dict = { + rm_item_code: { + "sco_rm_detail": rm_item_data.get("name"), + "item_name": rm_item_data["item_name"], + "description": item_wh.get(rm_item_code, {}).get("description", ""), + "qty": rm_item_data["qty"], + "from_warehouse": rm_item_data["warehouse"], + "stock_uom": rm_item_data["stock_uom"], + "serial_no": rm_item_data.get("serial_no"), + "batch_no": rm_item_data.get("batch_no"), + "main_item_code": rm_item_data["item_code"], + "allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"), + } + } + stock_entry.add_to_stock_entry_detail(items_dict) + return stock_entry.as_dict() + else: + frappe.throw(_("No Items selected for transfer")) + return subcontracting_order.name + + +def add_items_in_ste(ste_doc, row, qty, sco_rm_details, batch_no=None): + item = ste_doc.append("items", row.item_details) + + sco_rm_detail = list(set(row.sco_rm_details).intersection(sco_rm_details)) + item.update( + { + "qty": qty, + "batch_no": batch_no, + "basic_rate": row.item_details["rate"], + "sco_rm_detail": sco_rm_detail[0] if sco_rm_detail else "", + "s_warehouse": row.item_details["t_warehouse"], + "t_warehouse": row.item_details["s_warehouse"], + "item_code": row.item_details["rm_item_code"], + "subcontracted_item": row.item_details["main_item_code"], + "serial_no": "\n".join(row.serial_no) if row.serial_no else "", + } + ) + + +def make_return_stock_entry_for_subcontract(available_materials, sco_doc, sco_rm_details): + ste_doc = frappe.new_doc("Stock Entry") + ste_doc.purpose = "Material Transfer" + + ste_doc.subcontracting_order = sco_doc.name + ste_doc.company = sco_doc.company + ste_doc.is_return = 1 + + for key, value in available_materials.items(): + if not value.qty: + continue + + if value.batch_no: + for batch_no, qty in value.batch_no.items(): + if qty > 0: + add_items_in_ste(ste_doc, value, value.qty, sco_rm_details, batch_no) + else: + add_items_in_ste(ste_doc, value, value.qty, sco_rm_details) + + ste_doc.set_stock_entry_type() + ste_doc.calculate_rate_and_amount() + + return ste_doc + + +@frappe.whitelist() +def get_materials_from_supplier(subcontracting_order, sco_rm_details): + if isinstance(sco_rm_details, str): + sco_rm_details = json.loads(sco_rm_details) + + doc = frappe.get_cached_doc("Subcontracting Order", subcontracting_order) + doc.initialized_fields() + doc.subcontracting_orders = [doc.name] + doc.get_available_materials() + + if not doc.available_materials: + frappe.throw( + _("Materials are already received against the Subcontracting Order {0}").format( + subcontracting_order + ) + ) + + return make_return_stock_entry_for_subcontract(doc.available_materials, doc, sco_rm_details) + + +@frappe.whitelist() +def update_subcontracting_order_status(sco): + if isinstance(sco, str): + sco = frappe.get_doc("Subcontracting Order", sco) + + sco.update_status() diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_dashboard.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_dashboard.py new file mode 100644 index 0000000000..f17d8cd961 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_dashboard.py @@ -0,0 +1,8 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "subcontracting_order", + "transactions": [{"label": _("Reference"), "items": ["Subcontracting Receipt", "Stock Entry"]}], + } diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js new file mode 100644 index 0000000000..a2b724546b --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js @@ -0,0 +1,16 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.listview_settings['Subcontracting Order'] = { + get_indicator: function (doc) { + const status_colors = { + "Draft": "grey", + "Open": "orange", + "Partially Received": "yellow", + "Completed": "green", + "Partial Material Transferred": "purple", + "Material Transferred": "blue", + }; + return [__(doc.status), status_colors[doc.status], "status,=," + doc.status]; + }, +}; \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py new file mode 100644 index 0000000000..f58c8307e4 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -0,0 +1,8 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + +class TestSubcontractingOrder(FrappeTestCase): + pass \ No newline at end of file From 409df263e89da4e11e90277def8ae18d17178910 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 19 Apr 2022 14:57:31 +0530 Subject: [PATCH 11/98] refactor!: change "is_subcontracted" field type from "Select" to "Check" --- .../purchase_invoice/purchase_invoice.js | 6 ++--- .../purchase_invoice/purchase_invoice.json | 9 +++---- .../purchase_invoice/test_purchase_invoice.py | 6 ++--- .../purchase_invoice_item.json | 2 +- .../report/tax_detail/test_tax_detail.json | 2 +- .../doctype/purchase_order/purchase_order.js | 4 +-- .../purchase_order/purchase_order.json | 13 ++++++---- .../doctype/purchase_order/purchase_order.py | 6 ++--- .../purchase_order/test_purchase_order.py | 20 +++++++------- .../doctype/purchase_order/test_records.json | 4 +-- .../purchase_order_item.json | 10 ++++--- .../supplier_quotation.json | 5 ++-- .../supplier_quotation/test_records.json | 2 +- .../subcontract_order_summary.js | 2 +- .../subcontract_order_summary.py | 2 +- .../subcontracted_item_to_be_received.py | 2 +- .../test_subcontracted_item_to_be_received.py | 2 +- ...tracted_raw_materials_to_be_transferred.py | 2 +- ...tracted_raw_materials_to_be_transferred.py | 2 +- erpnext/controllers/accounts_controller.py | 2 +- erpnext/controllers/buying_controller.py | 11 +++----- erpnext/controllers/subcontracting.py | 2 +- erpnext/manufacturing/doctype/bom/test_bom.py | 2 +- .../production_plan/production_plan.py | 2 +- erpnext/patches.txt | 1 + .../change_is_subcontracted_fieldtype.py | 26 +++++++++++++++++++ erpnext/public/js/controllers/buying.js | 2 +- erpnext/public/js/controllers/transaction.js | 2 +- erpnext/public/js/utils.js | 4 +-- .../item_alternative/test_item_alternative.py | 2 +- .../purchase_receipt/purchase_receipt.js | 6 ++--- .../purchase_receipt/purchase_receipt.json | 9 +++---- .../purchase_receipt/test_purchase_receipt.py | 10 +++---- .../purchase_receipt/test_records.json | 2 +- .../purchase_receipt_item.json | 2 +- .../stock/doctype/stock_entry/stock_entry.js | 2 +- .../test_stock_ledger_entry.py | 2 +- erpnext/stock/get_item_details.py | 8 +++--- erpnext/stock/stock_ledger.py | 2 +- .../subcontracting_order.js | 2 +- erpnext/tests/test_subcontracting.py | 26 +++++++++---------- 41 files changed, 127 insertions(+), 101 deletions(-) create mode 100644 erpnext/patches/v14_0/change_is_subcontracted_fieldtype.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 5f6e61090b..ee29d2a744 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -141,7 +141,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. }) }, __("Get Items From")); } - this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes"); + this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted); if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) { frappe.model.with_doc("Supplier", me.frm.doc.supplier, function() { @@ -571,10 +571,10 @@ frappe.ui.form.on("Purchase Invoice", { }, is_subcontracted: function(frm) { - if (frm.doc.is_subcontracted === "Yes") { + if (frm.doc.is_subcontracted) { erpnext.buying.get_default_bom(frm); } - frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes"); + frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted); }, update_stock: function(frm) { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index bd0116443f..9f87c5ab54 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -543,11 +543,10 @@ "fieldtype": "Column Break" }, { - "default": "No", + "default": "0", "fieldname": "is_subcontracted", - "fieldtype": "Select", - "label": "Raw Materials Supplied", - "options": "No\nYes", + "fieldtype": "Check", + "label": "Is Subcontracted", "print_hide": 1 }, { @@ -1366,7 +1365,7 @@ "width": "50px" }, { - "depends_on": "eval:doc.update_stock && doc.is_subcontracted==\"Yes\"", + "depends_on": "eval:doc.update_stock && doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", "label": "Supplier Warehouse", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 843f66d546..73390dd6f4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -901,7 +901,7 @@ class TestPurchaseInvoice(unittest.TestCase): ) pi = make_purchase_invoice( - item_code="_Test FG Item", qty=10, rate=500, update_stock=1, is_subcontracted="Yes" + item_code="_Test FG Item", qty=10, rate=500, update_stock=1, is_subcontracted=1 ) self.assertEqual(len(pi.get("supplied_items")), 2) @@ -1611,7 +1611,7 @@ def make_purchase_invoice(**args): pi.conversion_rate = args.conversion_rate or 1 pi.is_return = args.is_return pi.return_against = args.return_against - pi.is_subcontracted = args.is_subcontracted or "No" + pi.is_subcontracted = args.is_subcontracted or 0 pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC" pi.cost_center = args.parent_cost_center @@ -1674,7 +1674,7 @@ def make_purchase_invoice_against_cost_center(**args): pi.is_return = args.is_return pi.is_return = args.is_return pi.credit_to = args.return_against or "Creditors - _TC" - pi.is_subcontracted = args.is_subcontracted or "No" + pi.is_subcontracted = args.is_subcontracted or 0 if args.supplier_warehouse: pi.supplier_warehouse = "_Test Warehouse 1 - _TC" diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index f9b2efd053..6651195e5f 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -623,7 +623,7 @@ }, { "default": "0", - "depends_on": "eval:parent.is_subcontracted == 'Yes'", + "depends_on": "eval:parent.is_subcontracted", "fieldname": "include_exploded_items", "fieldtype": "Check", "label": "Include Exploded Items", diff --git a/erpnext/accounts/report/tax_detail/test_tax_detail.json b/erpnext/accounts/report/tax_detail/test_tax_detail.json index 3a4b175455..e4903167cb 100644 --- a/erpnext/accounts/report/tax_detail/test_tax_detail.json +++ b/erpnext/accounts/report/tax_detail/test_tax_detail.json @@ -302,7 +302,7 @@ "is_opening": "No", "is_paid": 0, "is_return": 0, - "is_subcontracted": "No", + "is_subcontracted": 0, "items": [ { "allow_zero_valuation_rate": 0, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 1f6de1aa7b..2cad1fb081 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -185,7 +185,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e if (doc.status != "On Hold") { if(flt(doc.per_received) < 100 && allow_receipt) { cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create')); - if(doc.is_subcontracted==="Yes" && me.has_unsupplied_items()) { + if(doc.is_subcontracted && me.has_unsupplied_items()) { cur_frm.add_custom_button(__('Material to Supplier'), function() { me.make_stock_entry(); }, __("Transfer")); } @@ -653,7 +653,7 @@ function set_schedule_date(frm) { frappe.provide("erpnext.buying"); frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) { - if (frm.doc.is_subcontracted === "Yes") { + if (frm.doc.is_subcontracted) { erpnext.buying.get_default_bom(frm); } }); diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 307b57607e..5e4bc60e8f 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -448,15 +448,18 @@ "print_hide": 1 }, { - "default": "No", + "fieldname": "col_break_warehouse", + "fieldtype": "Column Break" + }, + { + "default": "0", "fieldname": "is_subcontracted", - "fieldtype": "Select", - "label": "Supply Raw Materials", - "options": "No\nYes", + "fieldtype": "Check", + "label": "Is Subcontracted", "print_hide": 1 }, { - "depends_on": "eval:doc.is_subcontracted==\"Yes\"", + "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", "label": "Supplier Warehouse", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index e8b8b87b98..1945079171 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -307,7 +307,7 @@ class PurchaseOrder(BuyingController): self.set_status(update=True, status=status) self.update_requested_qty() self.update_ordered_qty() - if self.is_subcontracted == "Yes": + if self.is_subcontracted: self.update_reserved_qty_for_subcontract() self.notify_update() @@ -324,7 +324,7 @@ class PurchaseOrder(BuyingController): self.update_ordered_qty() self.validate_budget() - if self.is_subcontracted == "Yes": + if self.is_subcontracted: self.update_reserved_qty_for_subcontract() frappe.get_doc("Authorization Control").validate_approving_authority( @@ -344,7 +344,7 @@ class PurchaseOrder(BuyingController): if self.has_drop_ship_item(): self.update_delivered_qty_in_sales_order() - if self.is_subcontracted == "Yes": + if self.is_subcontracted: self.update_reserved_qty_for_subcontract() self.check_on_hold_or_closed_status() diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index e4fb970c3f..1a7f2dd5d9 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -390,7 +390,7 @@ class TestPurchaseOrder(FrappeTestCase): frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete() def test_update_child_uom_conv_factor_change(self): - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") + po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) total_reqd_qty = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")]) trans_item = json.dumps( @@ -573,7 +573,7 @@ class TestPurchaseOrder(FrappeTestCase): automatically_fetch_payment_terms(enable=0) def test_subcontracting(self): - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") + po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) self.assertEqual(len(po.get("supplied_items")), 2) def test_warehouse_company_validation(self): @@ -617,7 +617,7 @@ class TestPurchaseOrder(FrappeTestCase): "doctype": "Purchase Order", "company": "_Test Company", "supplier": "_Test Supplier", - "is_subcontracted": "No", + "is_subcontracted": 0, "schedule_date": add_days(nowdate(), 1), "currency": frappe.get_cached_value("Company", "_Test Company", "default_currency"), "conversion_factor": 1, @@ -764,7 +764,7 @@ class TestPurchaseOrder(FrappeTestCase): ) # Submit PO - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") + po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) bin2 = frappe.db.get_value( "Bin", @@ -919,7 +919,7 @@ class TestPurchaseOrder(FrappeTestCase): po = create_purchase_order( item_code=item_code, qty=1, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1, ) @@ -936,7 +936,7 @@ class TestPurchaseOrder(FrappeTestCase): po1 = create_purchase_order( item_code=item_code, qty=1, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0, ) @@ -957,7 +957,7 @@ class TestPurchaseOrder(FrappeTestCase): po = create_purchase_order( item_code=item_code, qty=order_qty, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", ) @@ -1050,7 +1050,7 @@ class TestPurchaseOrder(FrappeTestCase): po = create_purchase_order( item_code=item_code, qty=order_qty, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", do_not_save=True, ) @@ -1283,7 +1283,7 @@ def create_purchase_order(**args): po.schedule_date = add_days(nowdate(), 1) po.company = args.company or "_Test Company" po.supplier = args.supplier or "_Test Supplier" - po.is_subcontracted = args.is_subcontracted or "No" + po.is_subcontracted = args.is_subcontracted or 0 po.currency = args.currency or frappe.get_cached_value("Company", po.company, "default_currency") po.conversion_factor = args.conversion_factor or 1 po.supplier_warehouse = args.supplier_warehouse or None @@ -1309,7 +1309,7 @@ def create_purchase_order(**args): if not args.do_not_save: po.insert() if not args.do_not_submit: - if po.is_subcontracted == "Yes": + if po.is_subcontracted: supp_items = po.get("supplied_items") for d in supp_items: if not d.reserve_warehouse: diff --git a/erpnext/buying/doctype/purchase_order/test_records.json b/erpnext/buying/doctype/purchase_order/test_records.json index 74b8f1b429..896050ce43 100644 --- a/erpnext/buying/doctype/purchase_order/test_records.json +++ b/erpnext/buying/doctype/purchase_order/test_records.json @@ -8,7 +8,7 @@ "doctype": "Purchase Order", "base_grand_total": 5000.0, "grand_total": 5000.0, - "is_subcontracted": "Yes", + "is_subcontracted": 1, "naming_series": "_T-Purchase Order-", "base_net_total": 5000.0, "items": [ @@ -42,7 +42,7 @@ "doctype": "Purchase Order", "base_grand_total": 5000.0, "grand_total": 5000.0, - "is_subcontracted": "No", + "is_subcontracted": 0, "naming_series": "_T-Purchase Order-", "base_net_total": 5000.0, "items": [ diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index b4cdb18211..7f797cfd2f 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -574,6 +574,7 @@ "read_only": 1 }, { + "depends_on": "eval:parent.is_subcontracted", "fieldname": "bom", "fieldtype": "Link", "label": "BOM", @@ -583,6 +584,7 @@ }, { "default": "0", + "depends_on": "eval:parent.is_subcontracted", "fieldname": "include_exploded_items", "fieldtype": "Check", "hidden": 1, @@ -849,20 +851,20 @@ "print_hide": 1 }, { - "depends_on": "eval:parent.is_subcontracted == 'Yes'", + "depends_on": "eval:parent.is_subcontracted", "fieldname": "fg_item", "fieldtype": "Link", "label": "Finished Good Item", - "mandatory_depends_on": "eval:parent.is_subcontracted == 'Yes'", + "mandatory_depends_on": "eval:parent.is_subcontracted", "options": "Item" }, { "default": "1", - "depends_on": "eval:parent.is_subcontracted == 'Yes'", + "depends_on": "eval:parent.is_subcontracted", "fieldname": "fg_item_qty", "fieldtype": "Float", "label": "Finished Good Item Qty", - "mandatory_depends_on": "eval:parent.is_subcontracted == 'Yes'" + "mandatory_depends_on": "eval:parent.is_subcontracted" } ], "idx": 1, diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 567e41fb61..8d1939a101 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -773,11 +773,10 @@ "fieldtype": "Column Break" }, { - "default": "No", + "default": "0", "fieldname": "is_subcontracted", - "fieldtype": "Select", + "fieldtype": "Check", "label": "Is Subcontracted", - "options": "\nYes\nNo", "print_hide": 1 }, { diff --git a/erpnext/buying/doctype/supplier_quotation/test_records.json b/erpnext/buying/doctype/supplier_quotation/test_records.json index 0f835d2a40..8acac3210d 100644 --- a/erpnext/buying/doctype/supplier_quotation/test_records.json +++ b/erpnext/buying/doctype/supplier_quotation/test_records.json @@ -7,7 +7,7 @@ "doctype": "Supplier Quotation", "base_grand_total": 5000.0, "grand_total": 5000.0, - "is_subcontracted": "No", + "is_subcontracted": 0, "naming_series": "_T-Supplier Quotation-", "base_net_total": 5000.0, "items": [ diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js index 5ba52f1b21..6889322fb9 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js @@ -35,7 +35,7 @@ frappe.query_reports["Subcontract Order Summary"] = { return { filters: { docstatus: 1, - is_subcontracted: 'Yes', + is_subcontracted: 1, company: frappe.query_report.get_filter_value('company') } } diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py index 1b2705a7be..3d66637576 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py @@ -45,7 +45,7 @@ def get_subcontracted_orders(report_filters): def get_filters(report_filters): filters = [ ["Purchase Order", "docstatus", "=", 1], - ["Purchase Order", "is_subcontracted", "=", "Yes"], + ["Purchase Order", "is_subcontracted", "=", 1], [ "Purchase Order", "transaction_date", diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py index 004657b6e8..2e90de66ef 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py +++ b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py @@ -78,7 +78,7 @@ def get_data(data, filters): def get_po(filters): record_filters = [ - ["is_subcontracted", "=", "Yes"], + ["is_subcontracted", "=", 1], ["supplier", "=", filters.supplier], ["transaction_date", "<=", filters.to_date], ["transaction_date", ">=", filters.from_date], diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py index 26e4243eee..57f8741b5b 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py +++ b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py @@ -17,7 +17,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry class TestSubcontractedItemToBeReceived(FrappeTestCase): def test_pending_and_received_qty(self): - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") + po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) transfer_param = [] make_stock_entry( item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100 diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py index 98b18da4ac..6b8a3b140a 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py @@ -72,7 +72,7 @@ def get_po_items_to_supply(filters): ], filters=[ ["Purchase Order", "per_received", "<", "100"], - ["Purchase Order", "is_subcontracted", "=", "Yes"], + ["Purchase Order", "is_subcontracted", "=", 1], ["Purchase Order", "supplier", "=", filters.supplier], ["Purchase Order", "transaction_date", "<=", filters.to_date], ["Purchase Order", "transaction_date", ">=", filters.from_date], diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py index 401176d5ce..2791a26db7 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py @@ -19,7 +19,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry class TestSubcontractedItemToBeTransferred(FrappeTestCase): def test_pending_and_transferred_qty(self): po = create_purchase_order( - item_code="_Test FG Item", is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + item_code="_Test FG Item", is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) # Material Receipt of RMs diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3a20d3f232..8a9318e184 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2586,7 +2586,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.update_ordered_qty() parent.update_ordered_and_reserved_qty() parent.update_receiving_percentage() - if parent.is_subcontracted == "Yes": + if parent.is_subcontracted: parent.update_reserved_qty_for_subcontract() parent.create_raw_materials_supplied("supplied_items") parent.save() diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 4823e8b05c..6fdb002be0 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -167,7 +167,7 @@ class BuyingController(StockController, Subcontracting): _("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx) ) - if item.get("from_warehouse") and self.get("is_subcontracted") == "Yes": + if item.get("from_warehouse") and self.get("is_subcontracted"): frappe.throw( _( "Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor" @@ -339,10 +339,7 @@ class BuyingController(StockController, Subcontracting): return supplied_items_cost def validate_for_subcontracting(self): - if not self.is_subcontracted and self.sub_contracted_items: - frappe.throw(_("Please enter 'Is Subcontracted' as Yes or No")) - - if self.is_subcontracted == "Yes": + if self.is_subcontracted: 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)) @@ -363,14 +360,14 @@ class BuyingController(StockController, Subcontracting): item.bom = None def create_raw_materials_supplied(self, raw_material_table): - if self.is_subcontracted == "Yes": + if self.is_subcontracted: self.set_materials_for_subcontracted_items(raw_material_table) elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]: for item in self.get("items"): item.rm_supp_cost = 0.0 - if self.is_subcontracted == "No" and self.get("supplied_items"): + if not self.is_subcontracted and self.get("supplied_items"): self.set("supplied_items", []) @property diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py index 70830882ef..4bce06ff9b 100644 --- a/erpnext/controllers/subcontracting.py +++ b/erpnext/controllers/subcontracting.py @@ -407,7 +407,7 @@ class Subcontracting: def set_consumed_qty_in_po(self): # Update consumed qty back in the purchase order - if self.is_subcontracted != "Yes": + if not self.is_subcontracted: return self.__get_purchase_orders() diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 524f45bfc2..62fc0724e0 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -251,7 +251,7 @@ class TestBOM(FrappeTestCase): self.assertEqual(bom.items[2].rate, 0) # test in Purchase Order sourced_by_supplier is not added to Supplied Item po = create_purchase_order( - item_code=item_code, qty=1, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + item_code=item_code, qty=1, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1]) supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 89f9ca6d83..60b32b84b0 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -501,7 +501,7 @@ class ProductionPlan(Document): po = frappe.new_doc("Purchase Order") po.supplier = supplier po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate() - po.is_subcontracted = "Yes" + po.is_subcontracted = 1 for row in po_list: po_data = { "item_code": row.production_item, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 028834a0ec..a3bf78b532 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -4,6 +4,7 @@ erpnext.patches.v11_0.rename_production_order_to_work_order erpnext.patches.v13_0.add_bin_unique_constraint erpnext.patches.v11_0.refactor_naming_series erpnext.patches.v11_0.refactor_autoname_naming +erpnext.patches.v14_0.change_is_subcontracted_fieldtype execute:frappe.reload_doc("accounts", "doctype", "POS Payment Method") #2020-05-28 execute:frappe.reload_doc("HR", "doctype", "HR Settings") #2020-01-16 #2020-07-24 erpnext.patches.v4_2.update_requested_and_ordered_qty #2021-03-31 diff --git a/erpnext/patches/v14_0/change_is_subcontracted_fieldtype.py b/erpnext/patches/v14_0/change_is_subcontracted_fieldtype.py new file mode 100644 index 0000000000..ba919a756a --- /dev/null +++ b/erpnext/patches/v14_0/change_is_subcontracted_fieldtype.py @@ -0,0 +1,26 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe + + +def execute(): + for doctype in ["Purchase Order", "Purchase Receipt", "Purchase Invoice", "Supplier Quotation"]: + frappe.db.sql( + """ + UPDATE `tab{doctype}` + SET is_subcontracted = 0 + where is_subcontracted in ('', NULL, 'No')""".format( + doctype=doctype + ) + ) + frappe.db.sql( + """ + UPDATE `tab{doctype}` + SET is_subcontracted = 1 + where is_subcontracted = 'Yes'""".format( + doctype=doctype + ) + ) + + frappe.reload_doc(frappe.get_meta(doctype).module, "doctype", frappe.scrub(doctype)) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index a925470d60..0920ca04ee 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -81,7 +81,7 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac } this.frm.set_query("item_code", "items", function() { - if (me.frm.doc.is_subcontracted == "Yes") { + if (me.frm.doc.is_subcontracted) { return{ query: "erpnext.controllers.queries.item_query", filters:{ 'supplier': me.frm.doc.supplier, 'is_stock_item': 0 } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 23c2bd405c..57cbe91fa0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -239,7 +239,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe () => set_value('currency', currency), () => set_value('price_list_currency', currency), () => set_value('status', 'Draft'), - () => set_value('is_subcontracted', 'No'), + () => set_value('is_subcontracted', 0), () => { if(this.frm.doc.company && !this.frm.doc.amended_from) { this.frm.trigger("company"); diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 8260426704..eded16529c 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -483,8 +483,8 @@ erpnext.utils.update_child_items = function(opts) { if (frm.doc.doctype == 'Sales Order') { filters = {"is_sales_item": 1}; } else if (frm.doc.doctype == 'Purchase Order') { - if (frm.doc.is_subcontracted == "Yes") { - filters = {"is_stock_item": 0}; + if (frm.doc.is_subcontracted) { + filters = {"is_sub_contracted_item": 1}; } else { filters = {"is_purchase_item": 1}; } diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py index d829b2cbf3..32c58c5ae1 100644 --- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py @@ -41,7 +41,7 @@ class TestItemAlternative(FrappeTestCase): supplier_warehouse = "Test Supplier Warehouse - _TC" po = create_purchase_order( item="Test Finished Goods - A", - is_subcontracted="Yes", + is_subcontracted=1, qty=5, rate=3000, supplier_warehouse=supplier_warehouse, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 0182ed55a1..51ec598f72 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -200,7 +200,7 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend cur_frm.add_custom_button(__('Reopen'), this.reopen_purchase_receipt, __("Status")) } - this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes"); + this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted); } make_purchase_invoice() { @@ -298,10 +298,10 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt frappe.provide("erpnext.buying"); frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) { - if (frm.doc.is_subcontracted === "Yes") { + if (frm.doc.is_subcontracted) { erpnext.buying.get_default_bom(frm); } - frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes"); + frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted); }); frappe.ui.form.on('Purchase Receipt Item', { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 6d4b4a19bd..6e5f6f5b52 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -437,17 +437,16 @@ "fieldtype": "Column Break" }, { - "default": "No", + "default": "0", "fieldname": "is_subcontracted", - "fieldtype": "Select", - "label": "Raw Materials Consumed", + "fieldtype": "Check", + "label": "Is Subcontracted", "oldfieldname": "is_subcontracted", "oldfieldtype": "Select", - "options": "No\nYes", "print_hide": 1 }, { - "depends_on": "eval:doc.is_subcontracted==\"Yes\"", + "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", "label": "Supplier Warehouse", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index a6f82b08dc..bfbdd56292 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -327,7 +327,7 @@ class TestPurchaseReceipt(FrappeTestCase): target="_Test Warehouse 1 - _TC", basic_rate=100, ) - pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted="Yes") + pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted=1) self.assertEqual(len(pr.get("supplied_items")), 2) rm_supp_cost = sum(d.amount for d in pr.get("supplied_items")) @@ -362,7 +362,7 @@ class TestPurchaseReceipt(FrappeTestCase): item_code="_Test FG Item", qty=10, rate=0, - is_subcontracted="Yes", + is_subcontracted=1, company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", supplier_warehouse="Work In Progress - TCP1", @@ -401,7 +401,7 @@ class TestPurchaseReceipt(FrappeTestCase): item_code=item_code, qty=1, include_exploded_items=0, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", ) @@ -1122,7 +1122,7 @@ class TestPurchaseReceipt(FrappeTestCase): po = create_purchase_order( item_code=item_code, qty=order_qty, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", ) @@ -1465,7 +1465,7 @@ def make_purchase_receipt(**args): pr.set_posting_time = 1 pr.company = args.company or "_Test Company" pr.supplier = args.supplier or "_Test Supplier" - pr.is_subcontracted = args.is_subcontracted or "No" + pr.is_subcontracted = args.is_subcontracted or 0 pr.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC" pr.currency = args.currency or "INR" pr.is_return = args.is_return diff --git a/erpnext/stock/doctype/purchase_receipt/test_records.json b/erpnext/stock/doctype/purchase_receipt/test_records.json index 724e3d729a..990ad12b30 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_records.json +++ b/erpnext/stock/doctype/purchase_receipt/test_records.json @@ -92,7 +92,7 @@ "currency": "INR", "doctype": "Purchase Receipt", "base_grand_total": 5000.0, - "is_subcontracted": "Yes", + "is_subcontracted": 1, "base_net_total": 5000.0, "items": [ { diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index e5994b2dd4..03a4201ce5 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -648,7 +648,7 @@ }, { "default": "0", - "depends_on": "eval:parent.is_subcontracted == 'Yes'", + "depends_on": "eval:parent.is_subcontracted", "fieldname": "include_exploded_items", "fieldtype": "Check", "label": "Include Exploded Items", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 1aafcee5bf..a94087821a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -793,7 +793,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle return { "filters": { "docstatus": 1, - "is_subcontracted": "Yes", + "is_subcontracted": 1, "company": me.frm.doc.company } }; diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 42956a129b..6561362c3a 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -436,7 +436,7 @@ class TestStockLedgerEntry(FrappeTestCase): item_code=subcontracted_item, qty=10, rate=20, - is_subcontracted="Yes", + is_subcontracted=1, ) self.assertEqual(pr1.items[0].valuation_rate, 120) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 0d7d47262a..dfd9f8ac63 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -50,7 +50,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru "transaction_date": None, "conversion_rate": 1.0, "buying_price_list": None, - "is_subcontracted": "Yes" / "No", + "is_subcontracted": 0/1, "ignore_pricing_rule": 0/1 "project": "" "set_warehouse": "" @@ -124,7 +124,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru if args.transaction_date and item.lead_time_days: out.schedule_date = out.lead_time_date = add_days(args.transaction_date, item.lead_time_days) - if args.get("is_subcontracted") == "Yes": + if args.get("is_subcontracted"): out.bom = args.get("bom") or get_default_bom(args.item_code) get_gross_profit(out) @@ -237,7 +237,7 @@ def validate_item_details(args, item): throw(_("Item {0} is a template, please select one of its variants").format(item.name)) elif args.transaction_type == "buying" and args.doctype != "Material Request": - if args.get("is_subcontracted") == "Yes" and item.is_stock_item: + if args.get("is_subcontracted") and item.is_stock_item: throw(_("Item {0} must be a Non-Stock Item").format(item.name)) @@ -258,7 +258,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): "transaction_date": None, "conversion_rate": 1.0, "buying_price_list": None, - "is_subcontracted": "Yes" / "No", + "is_subcontracted": 0/1, "ignore_pricing_rule": 0/1 "project": "", barcode: "", diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 967b2b2294..3e0ddab6d3 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -715,7 +715,7 @@ class update_entries_after(object): ) # Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice - if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted") == "Yes": + if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"): doc = frappe.get_doc(sle.voucher_type, sle.voucher_no) doc.update_valuation_rate(reset_outgoing_rate=False) for d in doc.items + doc.supplied_items: diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index 80fe94483b..c9e4577cea 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -24,7 +24,7 @@ frappe.ui.form.on('Subcontracting Order', { return { filters: { docstatus: 1, - is_subcontracted: "Yes" + is_subcontracted: 1 } }; }); diff --git a/erpnext/tests/test_subcontracting.py b/erpnext/tests/test_subcontracting.py index 07291e851b..bf12181c52 100644 --- a/erpnext/tests/test_subcontracting.py +++ b/erpnext/tests/test_subcontracting.py @@ -50,7 +50,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -112,7 +112,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -175,7 +175,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -239,7 +239,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -298,7 +298,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -363,7 +363,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -421,7 +421,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -492,7 +492,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -529,7 +529,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -609,7 +609,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -675,7 +675,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -751,7 +751,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -834,7 +834,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: From 3daf62dce862c02c33c76a3d670e906ea3901a8b Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 18 Apr 2022 08:21:01 +0530 Subject: [PATCH 12/98] feat: New DocType "Subcontracting Receipt Item" --- .../subcontracting_receipt_item/__init__.py | 0 .../subcontracting_receipt_item.json | 475 ++++++++++++++++++ .../subcontracting_receipt_item.py | 9 + 3 files changed, 484 insertions(+) create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt_item/__init__.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/__init__.py b/erpnext/subcontracting/doctype/subcontracting_receipt_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json new file mode 100644 index 0000000000..e2785ce0cd --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -0,0 +1,475 @@ +{ + "actions": [], + "autoname": "hash", + "creation": "2022-04-13 16:05:55.395695", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "column_break_2", + "item_name", + "section_break_4", + "description", + "brand", + "image_column", + "image", + "image_view", + "received_and_accepted", + "received_qty", + "qty", + "rejected_qty", + "returned_qty", + "col_break2", + "stock_uom", + "conversion_factor", + "tracking_section", + "col_break_tracking_section", + "rate_and_amount", + "rate", + "amount", + "column_break_19", + "rm_cost_per_qty", + "service_cost_per_qty", + "additional_cost_per_qty", + "rm_supp_cost", + "warehouse_and_reference", + "warehouse", + "rejected_warehouse", + "subcontracting_order", + "column_break_40", + "schedule_date", + "quality_inspection", + "subcontracting_order_item", + "subcontracting_receipt_item", + "section_break_45", + "bom", + "serial_no", + "col_break5", + "batch_no", + "rejected_serial_no", + "expense_account", + "manufacture_details", + "manufacturer", + "column_break_16", + "manufacturer_part_no", + "accounting_dimensions_section", + "project", + "dimension_col_break", + "cost_center", + "section_break_80", + "page_break" + ], + "fields": [ + { + "bold": 1, + "columns": 3, + "fieldname": "item_code", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "print_width": "100px", + "reqd": 1, + "search_index": 1, + "width": "100px" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Item Name", + "print_hide": 1, + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description", + "print_width": "300px", + "reqd": 1, + "width": "300px" + }, + { + "fieldname": "image", + "fieldtype": "Attach", + "hidden": 1, + "label": "Image" + }, + { + "fieldname": "image_view", + "fieldtype": "Image", + "label": "Image View", + "options": "image", + "print_hide": 1 + }, + { + "fieldname": "received_and_accepted", + "fieldtype": "Section Break", + "label": "Received and Accepted" + }, + { + "bold": 1, + "default": "0", + "fieldname": "received_qty", + "fieldtype": "Float", + "label": "Received Quantity", + "no_copy": 1, + "print_hide": 1, + "print_width": "100px", + "read_only": 1, + "reqd": 1, + "width": "100px" + }, + { + "columns": 2, + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Accepted Quantity", + "no_copy": 1, + "print_width": "100px", + "width": "100px" + }, + { + "columns": 1, + "fieldname": "rejected_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Rejected Quantity", + "no_copy": 1, + "print_hide": 1, + "print_width": "100px", + "width": "100px" + }, + { + "fieldname": "col_break2", + "fieldtype": "Column Break", + "print_hide": 1 + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "print_hide": 1, + "print_width": "100px", + "read_only": 1, + "reqd": 1, + "width": "100px" + }, + { + "default": "1", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "hidden": 1, + "label": "Conversion Factor", + "read_only": 1 + }, + { + "fieldname": "rate_and_amount", + "fieldtype": "Section Break", + "label": "Rate and Amount" + }, + { + "bold": 1, + "columns": 2, + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "options": "currency", + "print_width": "100px", + "width": "100px" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, + { + "fieldname": "rm_cost_per_qty", + "fieldtype": "Currency", + "label": "Raw Material Cost Per Qty", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "service_cost_per_qty", + "fieldtype": "Currency", + "label": "Service Cost Per Qty", + "read_only": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "additional_cost_per_qty", + "fieldtype": "Currency", + "label": "Additional Cost Per Qty", + "read_only": 1 + }, + { + "fieldname": "warehouse_and_reference", + "fieldtype": "Section Break", + "label": "Warehouse and Reference" + }, + { + "bold": 1, + "fieldname": "warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Accepted Warehouse", + "options": "Warehouse", + "print_hide": 1, + "print_width": "100px", + "width": "100px" + }, + { + "fieldname": "rejected_warehouse", + "fieldtype": "Link", + "label": "Rejected Warehouse", + "no_copy": 1, + "options": "Warehouse", + "print_hide": 1, + "print_width": "100px", + "width": "100px" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "quality_inspection", + "fieldtype": "Link", + "label": "Quality Inspection", + "no_copy": 1, + "options": "Quality Inspection", + "print_hide": 1 + }, + { + "fieldname": "column_break_40", + "fieldtype": "Column Break" + }, + { + "fieldname": "subcontracting_order", + "fieldtype": "Link", + "label": "Subcontracting Order", + "no_copy": 1, + "options": "Subcontracting Order", + "print_width": "150px", + "read_only": 1, + "search_index": 1, + "width": "150px" + }, + { + "fieldname": "schedule_date", + "fieldtype": "Date", + "label": "Required By", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_45", + "fieldtype": "Section Break" + }, + { + "depends_on": "eval:!doc.is_fixed_asset", + "fieldname": "serial_no", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Serial No", + "no_copy": 1 + }, + { + "depends_on": "eval:!doc.is_fixed_asset", + "fieldname": "batch_no", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Batch No", + "no_copy": 1, + "options": "Batch", + "print_hide": 1 + }, + { + "depends_on": "eval:!doc.is_fixed_asset", + "fieldname": "rejected_serial_no", + "fieldtype": "Small Text", + "label": "Rejected Serial No", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "subcontracting_order_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Subcontracting Order Item", + "no_copy": 1, + "print_hide": 1, + "print_width": "150px", + "read_only": 1, + "search_index": 1, + "width": "150px" + }, + { + "fieldname": "col_break5", + "fieldtype": "Column Break" + }, + { + "fieldname": "bom", + "fieldtype": "Link", + "label": "BOM", + "no_copy": 1, + "options": "BOM", + "print_hide": 1 + }, + { + "fetch_from": "item_code.brand", + "fieldname": "brand", + "fieldtype": "Link", + "hidden": 1, + "label": "Brand", + "options": "Brand", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "rm_supp_cost", + "fieldtype": "Currency", + "hidden": 1, + "label": "Raw Materials Supplied Cost", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, + "width": "150px" + }, + { + "fieldname": "expense_account", + "fieldtype": "Link", + "hidden": 1, + "label": "Expense Account", + "options": "Account", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "manufacture_details", + "fieldtype": "Section Break", + "label": "Manufacture" + }, + { + "fieldname": "manufacturer", + "fieldtype": "Link", + "label": "Manufacturer", + "options": "Manufacturer" + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "fieldname": "manufacturer_part_no", + "fieldtype": "Data", + "label": "Manufacturer Part Number" + }, + { + "fieldname": "subcontracting_receipt_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Subcontracting Receipt Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "image_column", + "fieldtype": "Column Break" + }, + { + "fieldname": "tracking_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "col_break_tracking_section", + "fieldtype": "Column Break" + }, + { + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project", + "print_hide": 1 + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "default": ":Company", + "depends_on": "eval:cint(erpnext.is_perpetual_inventory_enabled(parent.company))", + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center", + "print_hide": 1 + }, + { + "fieldname": "section_break_80", + "fieldtype": "Section Break" + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "page_break", + "fieldtype": "Check", + "label": "Page Break", + "print_hide": 1 + }, + { + "depends_on": "returned_qty", + "fieldname": "returned_qty", + "fieldtype": "Float", + "label": "Returned Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + } + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2022-04-21 12:07:55.899701", + "modified_by": "Administrator", + "module": "Subcontracting", + "name": "Subcontracting Receipt Item", + "naming_rule": "Random", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py new file mode 100644 index 0000000000..374f95baf3 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class SubcontractingReceiptItem(Document): + pass From 3b17584beea4efeec256b9af2c4e255058d38beb Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 21 Apr 2022 20:27:53 +0530 Subject: [PATCH 13/98] feat: New DocType "Subcontracting Receipt Supplied Item" --- .../__init__.py | 0 .../subcontracting_receipt_supplied_item.json | 198 ++++++++++++++++++ .../subcontracting_receipt_supplied_item.py | 9 + 3 files changed, 207 insertions(+) create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/__init__.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.py diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/__init__.py b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json new file mode 100644 index 0000000000..100a8060e8 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json @@ -0,0 +1,198 @@ +{ + "actions": [], + "creation": "2022-04-18 10:45:16.538479", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "main_item_code", + "rm_item_code", + "item_name", + "bom_detail_no", + "col_break1", + "description", + "stock_uom", + "conversion_factor", + "reference_name", + "secbreak_1", + "rate", + "col_break2", + "amount", + "secbreak_2", + "required_qty", + "col_break3", + "consumed_qty", + "current_stock", + "secbreak_3", + "batch_no", + "col_break4", + "serial_no", + "subcontracting_order" + ], + "fields": [ + { + "fieldname": "main_item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "rm_item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Raw Material Item Code", + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "in_global_search": 1, + "label": "Description", + "print_width": "300px", + "read_only": 1, + "width": "300px" + }, + { + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch No", + "no_copy": 1, + "options": "Batch" + }, + { + "fieldname": "serial_no", + "fieldtype": "Text", + "label": "Serial No", + "no_copy": 1 + }, + { + "fieldname": "col_break1", + "fieldtype": "Column Break" + }, + { + "fieldname": "required_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Available Qty For Consumption", + "print_hide": 1, + "read_only": 1 + }, + { + "columns": 2, + "fieldname": "consumed_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty to be Consumed", + "reqd": 1 + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock Uom", + "options": "UOM", + "read_only": 1 + }, + { + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "default": "1", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "hidden": 1, + "label": "Conversion Factor", + "read_only": 1 + }, + { + "fieldname": "current_stock", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Current Stock", + "read_only": 1 + }, + { + "fieldname": "reference_name", + "fieldtype": "Data", + "hidden": 1, + "in_list_view": 1, + "label": "Reference Name", + "read_only": 1 + }, + { + "fieldname": "bom_detail_no", + "fieldtype": "Data", + "hidden": 1, + "in_list_view": 1, + "label": "BOM Detail No", + "read_only": 1 + }, + { + "fieldname": "secbreak_1", + "fieldtype": "Section Break" + }, + { + "fieldname": "col_break2", + "fieldtype": "Column Break" + }, + { + "fieldname": "secbreak_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "col_break3", + "fieldtype": "Column Break" + }, + { + "fieldname": "secbreak_3", + "fieldtype": "Section Break" + }, + { + "fieldname": "col_break4", + "fieldtype": "Column Break" + }, + { + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only": 1 + }, + { + "fieldname": "subcontracting_order", + "fieldtype": "Link", + "hidden": 1, + "label": "Subcontracting Order", + "no_copy": 1, + "options": "Subcontracting Order", + "print_hide": 1, + "read_only": 1 + } + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2022-04-18 10:45:16.538479", + "modified_by": "Administrator", + "module": "Subcontracting", + "name": "Subcontracting Receipt Supplied Item", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "states": [] +} \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.py b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.py new file mode 100644 index 0000000000..f4d2805d4b --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class SubcontractingReceiptSuppliedItem(Document): + pass From 70a1f4062497818ef4ea1d6bc44e682af33f7038 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 21 Apr 2022 20:28:06 +0530 Subject: [PATCH 14/98] feat: New DocType "Subcontracting Receipt" --- .../controllers/sales_and_purchase_return.py | 9 +- erpnext/stock/doctype/serial_no/serial_no.py | 11 +- .../subcontracting_receipt/__init__.py | 0 .../subcontracting_receipt.js | 157 +++++ .../subcontracting_receipt.json | 645 ++++++++++++++++++ .../subcontracting_receipt.py | 197 ++++++ .../subcontracting_receipt_dashboard.py | 15 + .../subcontracting_receipt_list.js | 14 + .../test_subcontracting_receipt.py | 9 + 9 files changed, 1052 insertions(+), 5 deletions(-) create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/__init__.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_list.js create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index bdde3a1fd8..9642c24a9e 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -343,7 +343,7 @@ def make_return_doc(doctype, source_name, target_doc=None): # look for Print Heading "Debit Note" doc.select_print_heading = frappe.db.get_value("Print Heading", _("Debit Note")) - for tax in doc.get("taxes"): + for tax in doc.get("taxes") or []: if tax.charge_type == "Actual": tax.tax_amount = -1 * tax.tax_amount @@ -382,8 +382,11 @@ def make_return_doc(doctype, source_name, target_doc=None): for d in doc.get("packed_items"): d.qty = d.qty * -1 - doc.discount_amount = -1 * source.discount_amount - doc.run_method("calculate_taxes_and_totals") + if doc.get("discount_amount"): + doc.discount_amount = -1 * source.discount_amount + + if doctype != "Subcontracting Receipt": + doc.run_method("calculate_taxes_and_totals") def update_item(source_doc, target_doc, source_parent): target_doc.qty = -1 * source_doc.qty diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 316c897da0..c302454c66 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -687,7 +687,10 @@ def update_serial_nos_after_submit(controller, parentfield): update_rejected_serial_nos = ( True - if (controller.doctype in ("Purchase Receipt", "Purchase Invoice") and d.rejected_qty) + if ( + controller.doctype in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt") + and d.rejected_qty + ) else False ) accepted_serial_nos_updated = False @@ -700,7 +703,11 @@ def update_serial_nos_after_submit(controller, parentfield): qty = d.stock_qty else: warehouse = d.warehouse - qty = d.qty if controller.doctype == "Stock Reconciliation" else d.stock_qty + qty = ( + d.qty + if controller.doctype in ["Stock Reconciliation", "Subcontracting Receipt"] + else d.stock_qty + ) for sle in stock_ledger_entries: if sle.voucher_detail_no == d.name: if ( diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/__init__.py b/erpnext/subcontracting/doctype/subcontracting_receipt/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js new file mode 100644 index 0000000000..b98f979c66 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -0,0 +1,157 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.provide('erpnext.buying'); + +frappe.ui.form.on('Subcontracting Receipt', { + setup: (frm) => { + frm.get_field('supplied_items').grid.cannot_add_rows = true; + frm.get_field('supplied_items').grid.only_sortable(); + + frm.set_query('set_warehouse', () => { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); + + frm.set_query('rejected_warehouse', () => { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); + + frm.set_query('supplier_warehouse', () => { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); + + frm.set_query('warehouse', 'items', () => ({ + filters: { + company: frm.doc.company, + is_group: 0 + } + })); + + frm.set_query('rejected_warehouse', 'items', () => ({ + filters: { + company: frm.doc.company, + is_group: 0 + } + })); + }, + + refresh: (frm) => { + if (frm.doc.docstatus > 0) { + frm.add_custom_button(__("Stock Ledger"), function () { + frappe.route_options = { + voucher_no: frm.doc.name, + from_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), + company: frm.doc.company, + show_cancelled_entries: frm.doc.docstatus === 2 + }; + frappe.set_route("query-report", "Stock Ledger"); + }, __("View")); + + frm.add_custom_button(__('Accounting Ledger'), function () { + frappe.route_options = { + voucher_no: frm.doc.name, + from_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), + company: frm.doc.company, + group_by: "Group by Voucher (Consolidated)", + show_cancelled_entries: frm.doc.docstatus === 2 + }; + frappe.set_route("query-report", "General Ledger"); + }, __("View")); + } + + if (!frm.doc.is_return && frm.doc.docstatus == 1) { + frm.add_custom_button('Subcontract Return', function () { + frappe.model.open_mapped_doc({ + method: 'erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return', + frm: frm + }); + }, __('Create')); + frm.page.set_inner_btn_group_as_primary(__('Create')); + } + + if (frm.doc.docstatus == 0) { + frm.add_custom_button(__('Subcontracting Order'), function () { + if (!frm.doc.supplier) { + frappe.throw({ + title: __("Mandatory"), + message: __("Please Select a Supplier") + }); + } + + erpnext.utils.map_current_doc({ + method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_subcontracting_receipt', + source_doctype: "Subcontracting Order", + target: frm, + setters: { + supplier: frm.doc.supplier, + }, + get_query_filters: { + docstatus: 1, + per_received: ["<", 100], + company: frm.doc.company + } + }) + }, __("Get Items From")); + } + }, + + set_warehouse: (frm) => { + set_warehouse_in_children(frm.doc.items, 'warehouse', frm.doc.set_warehouse); + }, + + rejected_warehouse: (frm) => { + set_warehouse_in_children(frm.doc.items, 'rejected_warehouse', frm.doc.rejected_warehouse); + }, +}); + +frappe.ui.form.on('Subcontracting Receipt Item', { + item_code(frm) { + set_missing_values(frm); + }, + + qty(frm) { + set_missing_values(frm); + }, + + rate(frm) { + set_missing_values(frm); + }, +}); + +frappe.ui.form.on('Subcontracting Receipt Supplied Item', { + consumed_qty(frm) { + set_missing_values(frm); + }, +}); + +let set_warehouse_in_children = (child_table, warehouse_field, warehouse) => { + let transaction_controller = new erpnext.TransactionController(); + transaction_controller.autofill_warehouse(child_table, warehouse_field, warehouse); +} + +let set_missing_values = (frm) => { + frappe.call({ + doc: frm.doc, + method: 'set_missing_values', + callback: (r) => { + if (!r.exc) frm.refresh(); + }, + }); +}; \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json new file mode 100644 index 0000000000..e9638144a7 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -0,0 +1,645 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2022-04-18 11:20:44.226738", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "naming_series", + "supplier", + "supplier_name", + "column_break1", + "company", + "posting_date", + "posting_time", + "is_return", + "return_against", + "section_addresses", + "supplier_address", + "contact_person", + "address_display", + "contact_display", + "contact_mobile", + "contact_email", + "col_break_address", + "shipping_address", + "shipping_address_display", + "billing_address", + "billing_address_display", + "sec_warehouse", + "set_warehouse", + "rejected_warehouse", + "col_break_warehouse", + "supplier_warehouse", + "items_section", + "items", + "section_break0", + "total_qty", + "column_break_27", + "total", + "raw_material_details", + "get_current_stock", + "supplied_items", + "section_break_46", + "in_words", + "bill_no", + "bill_date", + "accounting_details_section", + "provisional_expense_account", + "more_info", + "status", + "column_break_39", + "per_returned", + "section_break_47", + "amended_from", + "range", + "column_break4", + "represents_company", + "subscription_detail", + "auto_repeat", + "printing_settings", + "letter_head", + "language", + "instructions", + "column_break_97", + "select_print_heading", + "other_details", + "remarks", + "transporter_info", + "transporter_name", + "column_break5", + "lr_no", + "lr_date" + ], + "fields": [ + { + "allow_on_submit": 1, + "default": "{supplier_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "MAT-SCR-.YYYY.-\nMAT-SCR-RET-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "bold": 1, + "fieldname": "supplier", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Supplier", + "options": "Supplier", + "print_hide": 1, + "print_width": "150px", + "reqd": 1, + "search_index": 1, + "width": "150px" + }, + { + "bold": 1, + "depends_on": "supplier", + "fetch_from": "supplier.supplier_name", + "fieldname": "supplier_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Supplier Name", + "read_only": 1 + }, + { + "fieldname": "column_break1", + "fieldtype": "Column Break", + "print_width": "50%", + "width": "50%" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "no_copy": 1, + "print_width": "100px", + "reqd": 1, + "search_index": 1, + "width": "100px" + }, + { + "description": "Time at which materials were received", + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time", + "no_copy": 1, + "print_hide": 1, + "print_width": "100px", + "reqd": 1, + "width": "100px" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "print_hide": 1, + "print_width": "150px", + "remember_last_selected_value": 1, + "reqd": 1, + "width": "150px" + }, + { + "collapsible": 1, + "fieldname": "section_addresses", + "fieldtype": "Section Break", + "label": "Address and Contact" + }, + { + "fieldname": "supplier_address", + "fieldtype": "Link", + "label": "Select Supplier Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Contact Person", + "options": "Contact", + "print_hide": 1 + }, + { + "fieldname": "address_display", + "fieldtype": "Small Text", + "label": "Address", + "read_only": 1 + }, + { + "fieldname": "contact_display", + "fieldtype": "Small Text", + "in_global_search": 1, + "label": "Contact", + "read_only": 1 + }, + { + "fieldname": "contact_mobile", + "fieldtype": "Small Text", + "label": "Mobile No", + "read_only": 1 + }, + { + "fieldname": "contact_email", + "fieldtype": "Small Text", + "label": "Contact Email", + "options": "Email", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "col_break_address", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_address", + "fieldtype": "Link", + "label": "Select Shipping Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "shipping_address_display", + "fieldtype": "Small Text", + "label": "Shipping Address", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "sec_warehouse", + "fieldtype": "Section Break" + }, + { + "description": "Sets 'Accepted Warehouse' in each row of the Items table.", + "fieldname": "set_warehouse", + "fieldtype": "Link", + "label": "Accepted Warehouse", + "options": "Warehouse", + "print_hide": 1 + }, + { + "description": "Sets 'Rejected Warehouse' in each row of the Items table.", + "fieldname": "rejected_warehouse", + "fieldtype": "Link", + "label": "Rejected Warehouse", + "no_copy": 1, + "options": "Warehouse", + "print_hide": 1 + }, + { + "fieldname": "col_break_warehouse", + "fieldtype": "Column Break" + }, + { + "fieldname": "supplier_warehouse", + "fieldtype": "Link", + "label": "Supplier Warehouse", + "no_copy": 1, + "options": "Warehouse", + "print_hide": 1, + "print_width": "50px", + "width": "50px" + }, + { + "fieldname": "items_section", + "fieldtype": "Section Break", + "options": "fa fa-shopping-cart" + }, + { + "allow_bulk_edit": 1, + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "options": "Subcontracting Receipt Item", + "reqd": 1 + }, + { + "depends_on": "supplied_items", + "fieldname": "get_current_stock", + "fieldtype": "Button", + "label": "Get Current Stock", + "options": "get_current_stock", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "supplied_items", + "depends_on": "supplied_items", + "fieldname": "raw_material_details", + "fieldtype": "Section Break", + "label": "Raw Materials Consumed", + "options": "fa fa-table", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "supplied_items", + "fieldtype": "Table", + "label": "Consumed Items", + "no_copy": 1, + "options": "Subcontracting Receipt Supplied Item", + "print_hide": 1 + }, + { + "fieldname": "section_break0", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_qty", + "fieldtype": "Float", + "label": "Total Quantity", + "read_only": 1 + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "fieldname": "total", + "fieldtype": "Currency", + "label": "Total", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "section_break_46", + "fieldtype": "Section Break" + }, + { + "fieldname": "in_words", + "fieldtype": "Data", + "label": "In Words", + "length": 240, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "bill_no", + "fieldtype": "Data", + "hidden": 1, + "label": "Bill No", + "print_hide": 1 + }, + { + "fieldname": "bill_date", + "fieldtype": "Date", + "hidden": 1, + "label": "Bill Date", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "More Information", + "options": "fa fa-file-text" + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "\nDraft\nCompleted\nReturn\nReturn Issued\nCancelled", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, + "reqd": 1, + "search_index": 1, + "width": "150px" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "options": "Subcontracting Receipt", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, + "width": "150px" + }, + { + "fieldname": "range", + "fieldtype": "Data", + "hidden": 1, + "label": "Range", + "print_hide": 1 + }, + { + "fieldname": "column_break4", + "fieldtype": "Column Break", + "print_hide": 1, + "print_width": "50%", + "width": "50%" + }, + { + "fieldname": "subscription_detail", + "fieldtype": "Section Break", + "label": "Auto Repeat Detail" + }, + { + "fieldname": "auto_repeat", + "fieldtype": "Link", + "label": "Auto Repeat", + "no_copy": 1, + "options": "Auto Repeat", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "printing_settings", + "fieldtype": "Section Break", + "label": "Printing Settings" + }, + { + "allow_on_submit": 1, + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "options": "Letter Head", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "select_print_heading", + "fieldtype": "Link", + "label": "Print Heading", + "no_copy": 1, + "options": "Print Heading", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "language", + "fieldtype": "Data", + "label": "Print Language", + "read_only": 1 + }, + { + "fieldname": "column_break_97", + "fieldtype": "Column Break" + }, + { + "fieldname": "other_details", + "fieldtype": "HTML", + "hidden": 1, + "label": "Other Details", + "options": "
Other Details
", + "print_hide": 1, + "print_width": "30%", + "width": "30%" + }, + { + "fieldname": "instructions", + "fieldtype": "Small Text", + "label": "Instructions" + }, + { + "fieldname": "remarks", + "fieldtype": "Small Text", + "label": "Remarks", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "transporter_name", + "fieldname": "transporter_info", + "fieldtype": "Section Break", + "label": "Transporter Details", + "options": "fa fa-truck" + }, + { + "fieldname": "transporter_name", + "fieldtype": "Data", + "label": "Transporter Name" + }, + { + "fieldname": "column_break5", + "fieldtype": "Column Break", + "print_width": "50%", + "width": "50%" + }, + { + "fieldname": "lr_no", + "fieldtype": "Data", + "label": "Vehicle Number", + "no_copy": 1, + "print_width": "100px", + "width": "100px" + }, + { + "fieldname": "lr_date", + "fieldtype": "Date", + "label": "Vehicle Date", + "no_copy": 1, + "print_width": "100px", + "width": "100px" + }, + { + "fieldname": "billing_address", + "fieldtype": "Link", + "label": "Select Billing Address", + "options": "Address" + }, + { + "fieldname": "billing_address_display", + "fieldtype": "Small Text", + "label": "Billing Address", + "read_only": 1 + }, + { + "fetch_from": "supplier.represents_company", + "fieldname": "represents_company", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Represents Company", + "options": "Company", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_details_section", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "provisional_expense_account", + "fieldtype": "Link", + "hidden": 1, + "label": "Provisional Expense Account", + "options": "Account" + }, + { + "default": "0", + "fieldname": "is_return", + "fieldtype": "Check", + "label": "Is Return", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "is_return", + "fieldname": "return_against", + "fieldtype": "Link", + "label": "Return Against Subcontracting Receipt", + "no_copy": 1, + "options": "Subcontracting Receipt", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_39", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:(!doc.__islocal && doc.is_return==0)", + "fieldname": "per_returned", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "% Returned", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_47", + "fieldtype": "Section Break" + } + ], + "is_submittable": 1, + "links": [], + "modified": "2022-04-18 13:15:12.011682", + "modified_by": "Administrator", + "module": "Subcontracting", + "name": "Subcontracting Receipt", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "read": 1, + "report": 1, + "role": "Accounts User" + }, + { + "permlevel": 1, + "read": 1, + "role": "Stock Manager", + "write": 1 + } + ], + "search_fields": "status, posting_date, supplier", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "timeline_field": "supplier", + "title_field": "title", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py new file mode 100644 index 0000000000..d80bbdf453 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -0,0 +1,197 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.utils import cint, flt, getdate, nowdate + +from erpnext.controllers.subcontracting_controller import SubcontractingController + + +class SubcontractingReceipt(SubcontractingController): + def __init__(self, *args, **kwargs): + super(SubcontractingReceipt, self).__init__(*args, **kwargs) + self.status_updater = [ + { + "target_dt": "Subcontracting Order Item", + "join_field": "subcontracting_order_item", + "target_field": "received_qty", + "target_parent_dt": "Subcontracting Order", + "target_parent_field": "per_received", + "target_ref_field": "qty", + "source_dt": "Subcontracting Receipt Item", + "source_field": "received_qty", + "percent_join_field": "subcontracting_order", + "overflow_type": "receipt", + }, + ] + + def update_status_updater_args(self): + if cint(self.is_return): + self.status_updater.extend( + [ + { + "source_dt": "Subcontracting Receipt Item", + "target_dt": "Subcontracting Order Item", + "join_field": "subcontracting_order_item", + "target_field": "returned_qty", + "source_field": "-1 * qty", + "extra_cond": """ and exists (select name from `tabSubcontracting Receipt` + where name=`tabSubcontracting Receipt Item`.parent and is_return=1)""", + }, + { + "source_dt": "Subcontracting Receipt Item", + "target_dt": "Subcontracting Receipt Item", + "join_field": "subcontracting_receipt_item", + "target_field": "returned_qty", + "target_parent_dt": "Subcontracting Receipt", + "target_parent_field": "per_returned", + "target_ref_field": "received_qty", + "source_field": "-1 * received_qty", + "percent_join_field_parent": "return_against", + }, + ] + ) + + def before_validate(self): + super(SubcontractingReceipt, self).before_validate() + self.set_items_cost_center() + self.set_items_expense_account() + + def validate(self): + super(SubcontractingReceipt, self).validate() + self.set_missing_values() + self.validate_posting_time() + self.validate_rejected_warehouse() + + if self._action == "submit": + self.make_batches("warehouse") + + if getdate(self.posting_date) > getdate(nowdate()): + frappe.throw(_("Posting Date cannot be future date")) + + self.reset_default_field_value("set_warehouse", "items", "warehouse") + self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse") + self.get_current_stock() + + def on_submit(self): + self.update_status_updater_args() + self.update_prevdoc_status() + self.set_subcontracting_order_status() + self.set_consumed_qty_in_sco() + self.update_stock_ledger() + + from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit + + update_serial_nos_after_submit(self, "items") + + self.make_gl_entries() + self.repost_future_sle_and_gle() + self.update_status() + + def on_cancel(self): + self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation") + self.update_status_updater_args() + self.update_prevdoc_status() + self.update_stock_ledger() + self.make_gl_entries_on_cancel() + self.repost_future_sle_and_gle() + self.delete_auto_created_batches() + self.set_consumed_qty_in_sco() + self.set_subcontracting_order_status() + self.update_status() + + @frappe.whitelist() + def set_missing_values(self): + self.set_missing_values_in_supplied_items() + self.set_missing_values_in_items() + + def set_missing_values_in_supplied_items(self): + for item in self.get("supplied_items") or []: + item.amount = item.rate * item.consumed_qty + + def set_missing_values_in_items(self): + rm_supp_cost = {} + for item in self.get("supplied_items") or []: + if item.reference_name in rm_supp_cost: + rm_supp_cost[item.reference_name] += item.amount + else: + rm_supp_cost[item.reference_name] = item.amount + + total_qty = total_amount = 0 + for item in self.items: + if item.name in rm_supp_cost: + item.rm_supp_cost = rm_supp_cost[item.name] + item.rm_cost_per_qty = item.rm_supp_cost / item.qty + rm_supp_cost.pop(item.name) + + if self.is_new() and item.rm_supp_cost > 0: + item.rate = item.rm_cost_per_qty + (item.service_cost_per_qty or 0) + item.additional_cost_per_qty + + item.received_qty = item.qty + (item.rejected_qty or 0) + item.amount = item.qty * item.rate + total_qty += item.qty + total_amount += item.amount + else: + self.total_qty = total_qty + self.total = total_amount + + def validate_rejected_warehouse(self): + if not self.rejected_warehouse: + for item in self.items: + if item.rejected_qty: + frappe.throw( + _("Rejected Warehouse is mandatory against rejected Item {0}").format(item.item_code) + ) + + def set_items_cost_center(self): + if self.company: + cost_center = frappe.get_cached_value("Company", self.company, "cost_center") + + for item in self.items: + if not item.cost_center: + item.cost_center = cost_center + + def set_items_expense_account(self): + if self.company: + expense_account = self.get_company_default("default_expense_account", ignore_validation=True) + + for item in self.items: + if not item.expense_account: + item.expense_account = expense_account + + @frappe.whitelist() + def get_current_stock(self): + for item in self.get("supplied_items"): + if self.supplier_warehouse: + actual_qty = frappe.db.get_value( + "Bin", + {"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse}, + "actual_qty", + ) + item.current_stock = flt(actual_qty) or 0 + + def update_status(self, status=None, update_modified=False): + if self.docstatus >= 1 and not status: + if self.docstatus == 1: + if self.is_return: + status = "Return" + return_against = frappe.get_doc("Subcontracting Receipt", self.return_against) + return_against.run_method("update_status") + else: + if self.per_returned == 100: + status = "Return Issued" + elif self.status == "Draft": + status = "Completed" + elif self.docstatus == 2: + status = "Cancelled" + + if status: + frappe.db.set_value("Subcontracting Receipt", self.name, "status", status, update_modified) + + +@frappe.whitelist() +def make_subcontract_return(source_name, target_doc=None): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + return make_return_doc("Subcontracting Receipt", source_name, target_doc) \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py new file mode 100644 index 0000000000..a9e51937d7 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py @@ -0,0 +1,15 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "subcontracting_receipt_no", + "internal_links": { + "Subcontracting Order": ["items", "subcontracting_order"], + "Project": ["items", "project"], + "Quality Inspection": ["items", "quality_inspection"], + }, + "transactions": [ + {"label": _("Reference"), "items": ["Subcontracting Order", "Quality Inspection", "Project"]}, + ], + } diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_list.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_list.js new file mode 100644 index 0000000000..6d961de8c6 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_list.js @@ -0,0 +1,14 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.listview_settings['Subcontracting Receipt'] = { + get_indicator: function (doc) { + const status_colors = { + "Draft": "grey", + "Return": "gray", + "Return Issued": "grey", + "Completed": "green", + }; + return [__(doc.status), status_colors[doc.status], "status,=," + doc.status]; + }, +}; \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py new file mode 100644 index 0000000000..bc41dca319 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestSubcontractingReceipt(FrappeTestCase): + pass From 574181f3d789e9010920d04d8746d242edf67100 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 26 Apr 2022 18:20:57 +0530 Subject: [PATCH 15/98] test: SubcontractingController --- .../tests/test_subcontracting_controller.py | 1024 +++++++++++++++++ 1 file changed, 1024 insertions(+) create mode 100644 erpnext/controllers/tests/test_subcontracting_controller.py diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py new file mode 100644 index 0000000000..ff588be643 --- /dev/null +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -0,0 +1,1024 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import copy +from collections import defaultdict + +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import cint + +from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order +from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry +from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + get_materials_from_supplier, + make_rm_stock_entry, + make_subcontracting_receipt, +) + + +class TestSubcontractingController(FrappeTestCase): + def setUp(self): + make_subcontracted_items() + make_raw_materials() + make_service_items() + make_bom_for_subcontracted_items() + + def test_remove_empty_rows(self): + sco = get_subcontracting_order() + len_before = len(sco.service_items) + sco.service_items[0].item_code = None + sco.remove_empty_rows() + self.assertEqual((len_before - 1), len(sco.service_items)) + + def test_create_raw_materials_supplied(self): + sco = get_subcontracting_order() + sco.supplied_items = None + sco.create_raw_materials_supplied() + self.assertIsNotNone(sco.supplied_items) + + def test_sco_with_bom(self): + """ + - Set backflush based on BOM. + - Create SCO for the item Subcontracted Item SA1 and add same item two times. + - Transfer the components from Stores to Supplier warehouse with batch no and serial nos. + - Create SCR against the SCO and check serial nos and batch no. + """ + + set_backflush_based_on("BOM") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 5, + "rate": 100, + "fg_item": "Subcontracted Item SA1", + "fg_item_qty": 5, + }, + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 6, + "rate": 100, + "fg_item": "Subcontracted Item SA1", + "fg_item_qty": 6, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + scr = make_subcontracting_receipt(sco.name) + scr.save() + scr.submit() + + for key, value in get_supplied_items(scr).items(): + transferred_detais = itemwise_details.get(key) + + for field in ["qty", "serial_no", "batch_no"]: + if value.get(field): + transfer, consumed = (transferred_detais.get(field), value.get(field)) + if field == "serial_no": + transfer, consumed = (sorted(transfer), sorted(consumed)) + + self.assertEqual(transfer, consumed) + + def test_sco_with_material_transfer(self): + """ + - Set backflush based on Material Transfer. + - Create SCO for the item Subcontracted Item SA1 and Subcontracted Item SA5. + - Transfer the components from Stores to Supplier warehouse with batch no and serial nos. + - Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5. + - Create partial SCR against the SCO and check serial nos and batch no. + """ + + set_backflush_based_on("Material Transferred for Subcontract") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 5, + "rate": 100, + "fg_item": "Subcontracted Item SA1", + "fg_item_qty": 5, + }, + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 5", + "qty": 6, + "rate": 100, + "fg_item": "Subcontracted Item SA5", + "fg_item_qty": 6, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + rm_items.append( + { + "main_item_code": "Subcontracted Item SA5", + "item_code": "Subcontracted SRM Item 4", + "qty": 6, + } + ) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.remove(scr1.items[1]) + scr1.save() + scr1.submit() + + for key, value in get_supplied_items(scr1).items(): + transferred_detais = itemwise_details.get(key) + + for field in ["qty", "serial_no", "batch_no"]: + if value.get(field): + self.assertEqual(value.get(field), transferred_detais.get(field)) + + scr2 = make_subcontracting_receipt(sco.name) + scr2.save() + scr2.submit() + + for key, value in get_supplied_items(scr2).items(): + transferred_detais = itemwise_details.get(key) + + for field in ["qty", "serial_no", "batch_no"]: + if value.get(field): + self.assertEqual(value.get(field), transferred_detais.get(field)) + + def test_subcontracting_with_same_components_different_fg(self): + """ + - Set backflush based on Material Transfer. + - Create SCO for the item Subcontracted Item SA2 and Subcontracted Item SA3. + - Transfer the components from Stores to Supplier warehouse with serial nos. + - Transfer extra qty of components for the item Subcontracted Item SA2. + - Create partial SCR against the SCO and check serial nos. + """ + + set_backflush_based_on("Material Transferred for Subcontract") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 2", + "qty": 5, + "rate": 100, + "fg_item": "Subcontracted Item SA2", + "fg_item_qty": 5, + }, + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 3", + "qty": 6, + "rate": 100, + "fg_item": "Subcontracted Item SA3", + "fg_item_qty": 6, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + rm_items[0]["qty"] += 1 + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.items[0].qty = 3 + scr1.remove(scr1.items[1]) + scr1.save() + scr1.submit() + + for key, value in get_supplied_items(scr1).items(): + transferred_detais = itemwise_details.get(key) + + self.assertEqual(value.qty, 4) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:4])) + + scr2 = make_subcontracting_receipt(sco.name) + scr2.items[0].qty = 2 + scr2.remove(scr2.items[1]) + scr2.save() + scr2.submit() + + for key, value in get_supplied_items(scr2).items(): + transferred_detais = itemwise_details.get(key) + + self.assertEqual(value.qty, 2) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[4:6])) + + scr3 = make_subcontracting_receipt(sco.name) + scr3.save() + scr3.submit() + + for key, value in get_supplied_items(scr3).items(): + transferred_detais = itemwise_details.get(key) + + self.assertEqual(value.qty, 6) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[6:12])) + + def test_return_non_consumed_materials(self): + """ + - Set backflush based on Material Transfer. + - Create SCO for item Subcontracted Item SA2. + - Transfer the components from Stores to Supplier warehouse with serial nos. + - Transfer extra qty of component for the subcontracted item Subcontracted Item SA2. + - Create SCR for full qty against the SCO and change the qty of raw material. + - After that return the non consumed material back to the store from supplier's warehouse. + """ + + set_backflush_based_on("Material Transferred for Subcontract") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 2", + "qty": 5, + "rate": 100, + "fg_item": "Subcontracted Item SA2", + "fg_item_qty": 5, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + rm_items[0]["qty"] += 1 + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.save() + scr1.supplied_items[0].consumed_qty = 5 + scr1.supplied_items[0].serial_no = "\n".join( + sorted(itemwise_details.get("Subcontracted SRM Item 2").get("serial_no")[0:5]) + ) + scr1.submit() + + for key, value in get_supplied_items(scr1).items(): + transferred_detais = itemwise_details.get(key) + self.assertEqual(value.qty, 5) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:5])) + + sco.load_from_db() + self.assertEqual(sco.supplied_items[0].consumed_qty, 5) + doc = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items]) + self.assertEqual(doc.items[0].qty, 1) + self.assertEqual(doc.items[0].s_warehouse, "_Test Warehouse 1 - _TC") + self.assertEqual(doc.items[0].t_warehouse, "_Test Warehouse - _TC") + self.assertEqual( + get_serial_nos(doc.items[0].serial_no), + itemwise_details.get(doc.items[0].item_code)["serial_no"][5:6], + ) + + def test_item_with_batch_based_on_bom(self): + """ + - Set backflush based on BOM. + - Create SCO for item Subcontracted Item SA4 (has batch no). + - Transfer the components from Stores to Supplier warehouse with batch no and serial nos. + - Transfer the components in multiple batches. + - Create the 3 SCR against the SCO and split Subcontracted Items into two batches. + - Keep the qty as 2 for Subcontracted Item in the SCR. + """ + + set_backflush_based_on("BOM") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 4", + "qty": 10, + "rate": 100, + "fg_item": "Subcontracted Item SA4", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = [ + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 1", + "qty": 10.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 2", + "qty": 10.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 3", + "qty": 3.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 3", + "qty": 3.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 3", + "qty": 3.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 3", + "qty": 1.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + ] + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.items[0].qty = 2 + add_second_row_in_scr(scr1) + scr1.flags.ignore_mandatory = True + scr1.save() + scr1.set_missing_values() + scr1.submit() + + for key, value in get_supplied_items(scr1).items(): + self.assertEqual(value.qty, 4) + + scr2 = make_subcontracting_receipt(sco.name) + scr2.items[0].qty = 2 + add_second_row_in_scr(scr2) + scr2.flags.ignore_mandatory = True + scr2.save() + scr2.set_missing_values() + scr2.submit() + + for key, value in get_supplied_items(scr2).items(): + self.assertEqual(value.qty, 4) + + scr3 = make_subcontracting_receipt(sco.name) + scr3.items[0].qty = 2 + scr3.flags.ignore_mandatory = True + scr3.save() + scr3.set_missing_values() + scr3.submit() + + for key, value in get_supplied_items(scr3).items(): + self.assertEqual(value.qty, 2) + + def test_item_with_batch_based_on_material_transfer(self): + """ + - Set backflush based on Material Transferred for Subcontract. + - Create SCO for item Subcontracted Item SA4 (has batch no). + - Transfer the components from Stores to Supplier warehouse with batch no and serial nos. + - Transfer the components in multiple batches with extra 2 qty for the batched item. + - Create the 3 SCR against the SCO and split Subcontracted Items into two batches. + - Keep the qty as 2 for Subcontracted Item in the SCR. + - In the first SCR the batched raw materials will be consumed 2 extra qty. + """ + + set_backflush_based_on("Material Transferred for Subcontract") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 4", + "qty": 10, + "rate": 100, + "fg_item": "Subcontracted Item SA4", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = [ + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 1", + "qty": 10.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 2", + "qty": 10.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 3", + "qty": 3.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 3", + "qty": 3.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 3", + "qty": 3.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + { + "main_item_code": "Subcontracted Item SA4", + "item_code": "Subcontracted SRM Item 3", + "qty": 3.0, + "rate": 100.0, + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + ] + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.items[0].qty = 2 + add_second_row_in_scr(scr1) + scr1.flags.ignore_mandatory = True + scr1.save() + scr1.set_missing_values() + scr1.submit() + + for key, value in get_supplied_items(scr1).items(): + qty = 4 if key != "Subcontracted SRM Item 3" else 6 + self.assertEqual(value.qty, qty) + + scr2 = make_subcontracting_receipt(sco.name) + scr2.items[0].qty = 2 + add_second_row_in_scr(scr2) + scr2.flags.ignore_mandatory = True + scr2.save() + scr2.set_missing_values() + scr2.submit() + + for key, value in get_supplied_items(scr2).items(): + self.assertEqual(value.qty, 4) + + scr3 = make_subcontracting_receipt(sco.name) + scr3.items[0].qty = 2 + scr3.flags.ignore_mandatory = True + scr3.save() + scr3.set_missing_values() + scr3.submit() + + for key, value in get_supplied_items(scr3).items(): + self.assertEqual(value.qty, 1) + + def test_partial_transfer_serial_no_components_based_on_material_transfer(self): + """ + - Set backflush based on Material Transferred for Subcontract. + - Create SCO for the item Subcontracted Item SA2. + - Transfer the partial components from Stores to Supplier warehouse with serial nos. + - Create partial SCR against the SCO and change the qty manually. + - Transfer the remaining components from Stores to Supplier warehouse with serial nos. + - Create SCR for remaining qty against the SCO and change the qty manually. + """ + + set_backflush_based_on("Material Transferred for Subcontract") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 2", + "qty": 10, + "rate": 100, + "fg_item": "Subcontracted Item SA2", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + rm_items[0]["qty"] = 5 + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.items[0].qty = 5 + scr1.flags.ignore_mandatory = True + scr1.save() + scr1.set_missing_values() + + for key, value in get_supplied_items(scr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, 3) + self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3])) + + scr1.load_from_db() + scr1.supplied_items[0].consumed_qty = 5 + scr1.supplied_items[0].serial_no = "\n".join( + itemwise_details[scr1.supplied_items[0].rm_item_code]["serial_no"] + ) + scr1.save() + scr1.submit() + + for key, value in get_supplied_items(scr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(sorted(value.serial_no), sorted(details.serial_no)) + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr2 = make_subcontracting_receipt(sco.name) + scr2.submit() + + for key, value in get_supplied_items(scr2).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(sorted(value.serial_no), sorted(details.serial_no)) + + def test_incorrect_serial_no_components_based_on_material_transfer(self): + """ + - Set backflush based on Material Transferred for Subcontract. + - Create SCO for the item Subcontracted Item SA2. + - Transfer the serialized componenets to the supplier. + - Create SCR and change the serial no which is not transferred. + - System should throw the error and not allowed to save the SCR. + """ + + set_backflush_based_on("Material Transferred for Subcontract") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 2", + "qty": 10, + "rate": 100, + "fg_item": "Subcontracted Item SA2", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.save() + scr1.supplied_items[0].serial_no = "ABCD" + self.assertRaises(frappe.ValidationError, scr1.save) + scr1.delete() + + def test_partial_transfer_batch_based_on_material_transfer(self): + """ + - Set backflush based on Material Transferred for Subcontract. + - Create SCO for the item Subcontracted Item SA6. + - Transfer the partial components from Stores to Supplier warehouse with batch. + - Create partial SCR against the SCO and change the qty manually. + - Transfer the remaining components from Stores to Supplier warehouse with batch. + - Create SCR for remaining qty against the SCO and change the qty manually. + """ + + set_backflush_based_on("Material Transferred for Subcontract") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 6", + "qty": 10, + "rate": 100, + "fg_item": "Subcontracted Item SA6", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + rm_items[0]["qty"] = 5 + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.items[0].qty = 5 + scr1.save() + + transferred_batch_no = "" + for key, value in get_supplied_items(scr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, 3) + transferred_batch_no = details.batch_no + self.assertEqual(value.batch_no, details.batch_no) + + scr1.load_from_db() + scr1.supplied_items[0].consumed_qty = 5 + scr1.supplied_items[0].batch_no = list(transferred_batch_no.keys())[0] + scr1.save() + scr1.submit() + + for key, value in get_supplied_items(scr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(value.batch_no, details.batch_no) + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.submit() + + for key, value in get_supplied_items(scr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(value.batch_no, details.batch_no) + + +def add_second_row_in_scr(scr): + item_dict = {} + for column in [ + "item_code", + "item_name", + "qty", + "uom", + "warehouse", + "stock_uom", + "subcontracting_order", + "subcontracting_order_finished_good_item", + "conversion_factor", + "rate", + "expense_account", + "sco_rm_detail", + ]: + item_dict[column] = scr.items[0].get(column) + + scr.append("items", item_dict) + + +def get_supplied_items(scr_doc): + supplied_items = {} + for row in scr_doc.get("supplied_items"): + if row.rm_item_code not in supplied_items: + supplied_items.setdefault( + row.rm_item_code, frappe._dict({"qty": 0, "serial_no": [], "batch_no": defaultdict(float)}) + ) + + details = supplied_items[row.rm_item_code] + update_item_details(row, details) + + return supplied_items + + +def make_stock_in_entry(**args): + args = frappe._dict(args) + + items = {} + for row in args.rm_items: + row = frappe._dict(row) + + doc = make_stock_entry( + target=row.warehouse or "_Test Warehouse - _TC", + item_code=row.item_code, + qty=row.qty or 1, + basic_rate=row.rate or 100, + ) + + if row.item_code not in items: + items.setdefault( + row.item_code, frappe._dict({"qty": 0, "serial_no": [], "batch_no": defaultdict(float)}) + ) + + child_row = doc.items[0] + details = items[child_row.item_code] + update_item_details(child_row, details) + + return items + + +def update_item_details(child_row, details): + details.qty += ( + child_row.get("qty") + if child_row.doctype == "Stock Entry Detail" + else child_row.get("consumed_qty") + ) + + if child_row.serial_no: + details.serial_no.extend(get_serial_nos(child_row.serial_no)) + + if child_row.batch_no: + details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty") + + +def make_stock_transfer_entry(**args): + args = frappe._dict(args) + + items = [] + for row in args.rm_items: + row = frappe._dict(row) + + item = { + "item_code": row.main_item_code or args.main_item_code, + "rm_item_code": row.item_code, + "qty": row.qty or 1, + "item_name": row.item_code, + "rate": row.rate or 100, + "stock_uom": row.stock_uom or "Nos", + "warehouse": row.warehuose or "_Test Warehouse - _TC", + } + + item_details = args.itemwise_details.get(row.item_code) + + if item_details and item_details.serial_no: + serial_nos = item_details.serial_no[0 : cint(row.qty)] + item["serial_no"] = "\n".join(serial_nos) + item_details.serial_no = list(set(item_details.serial_no) - set(serial_nos)) + + if item_details and item_details.batch_no: + for batch_no, batch_qty in item_details.batch_no.items(): + if batch_qty >= row.qty: + item["batch_no"] = batch_no + item_details.batch_no[batch_no] -= row.qty + break + + items.append(item) + + ste_dict = make_rm_stock_entry(args.sco_no, items) + doc = frappe.get_doc(ste_dict) + doc.insert() + doc.submit() + + return doc + + +def make_subcontracted_items(): + sub_contracted_items = { + "Subcontracted Item SA1": {}, + "Subcontracted Item SA2": {}, + "Subcontracted Item SA3": {}, + "Subcontracted Item SA4": { + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "SBAT.####", + }, + "Subcontracted Item SA5": {}, + "Subcontracted Item SA6": {}, + "Subcontracted Item SA7": {}, + } + + for item, properties in sub_contracted_items.items(): + if not frappe.db.exists("Item", item): + properties.update({"is_stock_item": 1, "is_sub_contracted_item": 1}) + make_item(item, properties) + + +def make_raw_materials(): + raw_materials = { + "Subcontracted SRM Item 1": {}, + "Subcontracted SRM Item 2": {"has_serial_no": 1, "serial_no_series": "SRI.####"}, + "Subcontracted SRM Item 3": { + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BAT.####", + }, + "Subcontracted SRM Item 4": {"has_serial_no": 1, "serial_no_series": "SRII.####"}, + "Subcontracted SRM Item 5": {"has_serial_no": 1, "serial_no_series": "SRII.####"}, + } + + for item, properties in raw_materials.items(): + if not frappe.db.exists("Item", item): + properties.update({"is_stock_item": 1}) + make_item(item, properties) + + +def make_service_item(item, properties={}): + if not frappe.db.exists("Item", item): + properties.update({"is_stock_item": 0}) + make_item(item, properties) + + +def make_service_items(): + service_items = { + "Subcontracted Service Item 1": {}, + "Subcontracted Service Item 2": {}, + "Subcontracted Service Item 3": {}, + "Subcontracted Service Item 4": {}, + "Subcontracted Service Item 5": {}, + "Subcontracted Service Item 6": {}, + "Subcontracted Service Item 7": {}, + } + + for item, properties in service_items.items(): + make_service_item(item, properties) + + +def make_bom_for_subcontracted_items(): + boms = { + "Subcontracted Item SA1": [ + "Subcontracted SRM Item 1", + "Subcontracted SRM Item 2", + "Subcontracted SRM Item 3", + ], + "Subcontracted Item SA2": ["Subcontracted SRM Item 2"], + "Subcontracted Item SA3": ["Subcontracted SRM Item 2"], + "Subcontracted Item SA4": [ + "Subcontracted SRM Item 1", + "Subcontracted SRM Item 2", + "Subcontracted SRM Item 3", + ], + "Subcontracted Item SA5": ["Subcontracted SRM Item 5"], + "Subcontracted Item SA6": ["Subcontracted SRM Item 3"], + "Subcontracted Item SA7": ["Subcontracted SRM Item 1"], + } + + for item_code, raw_materials in boms.items(): + if not frappe.db.exists("BOM", {"item": item_code}): + make_bom(item=item_code, raw_materials=raw_materials, rate=100) + + +def set_backflush_based_on(based_on): + frappe.db.set_value( + "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", based_on + ) + + +def get_subcontracting_order(**args): + from erpnext.subcontracting.doctype.subcontracting_order.test_subcontracting_order import ( + create_subcontracting_order, + ) + + args = frappe._dict(args) + + if args.get("po_name"): + po = frappe.get_doc("Purchase Order", args.get("po_name")) + + if po.is_subcontracted: + return create_subcontracting_order(po_name=po.name, **args) + + if not args.service_items: + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 7", + "qty": 5, + "rate": 100, + "fg_item": "Subcontracted Item SA7", + "fg_item_qty": 10, + }, + ] + else: + service_items = args.service_items + + po = create_purchase_order( + rm_items=service_items, + is_subcontracted=1, + supplier_warehouse=args.supplier_warehouse or "_Test Warehouse 1 - _TC", + ) + + return create_subcontracting_order(po_name=po.name, **args) + + +def get_rm_items(supplied_items): + rm_items = [] + + for item in supplied_items: + rm_items.append( + { + "main_item_code": item.main_item_code, + "item_code": item.rm_item_code, + "qty": item.required_qty, + "rate": item.rate, + "stock_uom": item.stock_uom, + "warehouse": item.reserve_warehouse, + } + ) + + return rm_items + + +def make_subcontracted_item(**args): + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + + args = frappe._dict(args) + + if not frappe.db.exists("Item", args.item_code): + make_item( + args.item_code, + { + "is_stock_item": 1, + "is_sub_contracted_item": 1, + "has_batch_no": args.get("has_batch_no") or 0, + }, + ) + + if not args.raw_materials: + if not frappe.db.exists("Item", "Test Extra Item 1"): + make_item( + "Test Extra Item 1", + { + "is_stock_item": 1, + }, + ) + + if not frappe.db.exists("Item", "Test Extra Item 2"): + make_item( + "Test Extra Item 2", + { + "is_stock_item": 1, + }, + ) + + args.raw_materials = ["_Test FG Item", "Test Extra Item 1"] + + if not frappe.db.get_value("BOM", {"item": args.item_code}, "name"): + make_bom(item=args.item_code, raw_materials=args.get("raw_materials")) \ No newline at end of file From 8bc653b633895b40d278bf2745b1a223592d2cca Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 26 Apr 2022 18:23:12 +0530 Subject: [PATCH 16/98] test: SubcontractingOrder --- .../test_subcontracting_order.py | 526 +++++++++++++++++- 1 file changed, 524 insertions(+), 2 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index f58c8307e4..5644045df9 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -1,8 +1,530 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe +import copy + +import frappe from frappe.tests.utils import FrappeTestCase +from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_subcontracting_order +from erpnext.controllers.tests.test_subcontracting_controller import ( + get_rm_items, + get_subcontracting_order, + make_bom_for_subcontracted_items, + make_raw_materials, + make_service_items, + make_stock_in_entry, + make_stock_transfer_entry, + make_subcontracted_item, + make_subcontracted_items, + set_backflush_based_on, +) +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry +from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + make_rm_stock_entry, + make_subcontracting_receipt, +) + + class TestSubcontractingOrder(FrappeTestCase): - pass \ No newline at end of file + def setUp(self): + make_subcontracted_items() + make_raw_materials() + make_service_items() + make_bom_for_subcontracted_items() + + def test_populate_items_table(self): + sco = get_subcontracting_order() + sco.items = None + sco.populate_items_table() + self.assertEqual(len(sco.service_items), len(sco.items)) + + def test_set_missing_values(self): + sco = get_subcontracting_order() + before = {sco.total_qty, sco.total, sco.total_additional_costs} + sco.total_qty = sco.total = sco.total_additional_costs = 0 + sco.set_missing_values() + after = {sco.total_qty, sco.total, sco.total_additional_costs} + self.assertSetEqual(before, after) + + def test_update_status(self): + # Draft + sco = get_subcontracting_order(do_not_submit=1) + self.assertEqual(sco.status, "Draft") + + # Open + sco.submit() + sco.load_from_db() + self.assertEqual(sco.status, "Open") + + # Partial Material Transferred + rm_items = get_rm_items(sco.supplied_items) + rm_items[0]["qty"] -= 1 + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + sco.load_from_db() + self.assertEqual(sco.status, "Partial Material Transferred") + + # Material Transferred + rm_items[0]["qty"] = 1 + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + sco.load_from_db() + self.assertEqual(sco.status, "Material Transferred") + + # Partially Received + scr = make_subcontracting_receipt(sco.name) + scr.items[0].qty -= 1 + scr.save() + scr.submit() + sco.load_from_db() + self.assertEqual(sco.status, "Partially Received") + + # Completed + scr = make_subcontracting_receipt(sco.name) + scr.save() + scr.submit() + sco.load_from_db() + self.assertEqual(sco.status, "Completed") + + def test_make_rm_stock_entry(self): + sco = get_subcontracting_order() + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + ste = make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + self.assertEqual(len(ste.items), len(rm_items)) + + def test_make_rm_stock_entry_for_serial_items(self): + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 2", + "qty": 5, + "rate": 100, + "fg_item": "Subcontracted Item SA2", + "fg_item_qty": 5, + }, + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 5", + "qty": 6, + "rate": 100, + "fg_item": "Subcontracted Item SA5", + "fg_item_qty": 6, + }, + ] + + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + ste = make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + self.assertEqual(len(ste.items), len(rm_items)) + + def test_make_rm_stock_entry_for_batch_items(self): + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 4", + "qty": 5, + "rate": 100, + "fg_item": "Subcontracted Item SA4", + "fg_item_qty": 5, + }, + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 6", + "qty": 6, + "rate": 100, + "fg_item": "Subcontracted Item SA6", + "fg_item_qty": 6, + }, + ] + + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + ste = make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + self.assertEqual(len(ste.items), len(rm_items)) + + def test_update_reserved_qty_for_subcontracting(self): + # Make stock available for raw materials + make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) + make_stock_entry( + target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100 + ) + make_stock_entry( + target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=30, basic_rate=100 + ) + make_stock_entry( + target="_Test Warehouse 1 - _TC", + item_code="_Test Item Home Desktop 100", + qty=30, + basic_rate=100, + ) + + bin1 = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], + as_dict=1, + ) + + # Create SCO + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 10, + "rate": 100, + "fg_item": "_Test FG Item", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + + bin2 = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], + as_dict=1, + ) + + self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) + self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10) + self.assertNotEqual(bin1.modified, bin2.modified) + + # Create stock transfer + rm_items = [ + { + "item_code": "_Test FG Item", + "rm_item_code": "_Test Item", + "item_name": "_Test Item", + "qty": 6, + "warehouse": "_Test Warehouse - _TC", + "rate": 100, + "amount": 600, + "stock_uom": "Nos", + } + ] + ste = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items)) + ste.to_warehouse = "_Test Warehouse 1 - _TC" + ste.save() + ste.submit() + + bin3 = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", + as_dict=1, + ) + + self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + + make_stock_entry( + target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=40, basic_rate=100 + ) + make_stock_entry( + target="_Test Warehouse 1 - _TC", + item_code="_Test Item Home Desktop 100", + qty=40, + basic_rate=100, + ) + + # Make SCR against the SCO + scr = make_subcontracting_receipt(sco.name) + scr.save() + scr.submit() + + bin4 = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", + as_dict=1, + ) + + self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + + # Cancel SCR + scr.reload() + scr.cancel() + bin5 = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", + as_dict=1, + ) + + self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + + # Cancel Stock Entry + ste.cancel() + bin6 = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", + as_dict=1, + ) + + self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) + + # Cancel PO + sco.reload() + sco.cancel() + bin7 = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", + as_dict=1, + ) + + self.assertEqual(bin7.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + + def test_exploded_items(self): + item_code = "_Test Subcontracted FG Item 11" + make_subcontracted_item(item_code=item_code) + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 1, + "rate": 100, + "fg_item": item_code, + "fg_item_qty": 1, + }, + ] + + sco1 = get_subcontracting_order(service_items=service_items, include_exploded_items=1) + item_name = frappe.db.get_value("BOM", {"item": item_code}, "name") + bom = frappe.get_doc("BOM", item_name) + exploded_items = sorted([item.item_code for item in bom.exploded_items]) + supplied_items = sorted([item.rm_item_code for item in sco1.supplied_items]) + self.assertEqual(exploded_items, supplied_items) + + sco2 = get_subcontracting_order(service_items=service_items, include_exploded_items=0) + supplied_items1 = sorted([item.rm_item_code for item in sco2.supplied_items]) + bom_items = sorted([item.item_code for item in bom.items]) + self.assertEqual(supplied_items1, bom_items) + + def test_backflush_based_on_stock_entry(self): + item_code = "_Test Subcontracted FG Item 1" + make_subcontracted_item(item_code=item_code) + make_item("Sub Contracted Raw Material 1", {"is_stock_item": 1, "is_sub_contracted_item": 1}) + + set_backflush_based_on("Material Transferred for Subcontract") + + order_qty = 5 + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": order_qty, + "rate": 100, + "fg_item": item_code, + "fg_item_qty": order_qty, + }, + ] + + sco = get_subcontracting_order(service_items=service_items) + + make_stock_entry( + target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100 + ) + make_stock_entry( + target="_Test Warehouse - _TC", item_code="Test Extra Item 1", qty=100, basic_rate=100 + ) + make_stock_entry( + target="_Test Warehouse - _TC", item_code="Test Extra Item 2", qty=10, basic_rate=100 + ) + make_stock_entry( + target="_Test Warehouse - _TC", + item_code="Sub Contracted Raw Material 1", + qty=10, + basic_rate=100, + ) + + rm_items = [ + { + "item_code": item_code, + "rm_item_code": "Sub Contracted Raw Material 1", + "item_name": "_Test Item", + "qty": 10, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos", + }, + { + "item_code": item_code, + "rm_item_code": "_Test Item Home Desktop 100", + "item_name": "_Test Item Home Desktop 100", + "qty": 20, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos", + }, + { + "item_code": item_code, + "rm_item_code": "Test Extra Item 1", + "item_name": "Test Extra Item 1", + "qty": 10, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos", + }, + { + "item_code": item_code, + "rm_item_code": "Test Extra Item 2", + "stock_uom": "Nos", + "qty": 10, + "warehouse": "_Test Warehouse - _TC", + "item_name": "Test Extra Item 2", + }, + ] + + ste = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items)) + ste.submit() + + scr = make_subcontracting_receipt(sco.name) + received_qty = 2 + + # partial receipt + scr.get("items")[0].qty = received_qty + scr.save() + scr.submit() + + transferred_items = sorted( + [item.item_code for item in ste.get("items") if ste.subcontracting_order == sco.name] + ) + issued_items = sorted([item.rm_item_code for item in scr.get("supplied_items")]) + + self.assertEqual(transferred_items, issued_items) + self.assertEqual(scr.get_supplied_items_cost(scr.get("items")[0].name), 2000) + + transferred_rm_map = frappe._dict() + for item in rm_items: + transferred_rm_map[item.get("rm_item_code")] = item + + set_backflush_based_on("BOM") + + def test_supplied_qty(self): + item_code = "_Test Subcontracted FG Item 5" + make_item("Sub Contracted Raw Material 4", {"is_stock_item": 1, "is_sub_contracted_item": 1}) + + make_subcontracted_item(item_code=item_code, raw_materials=["Sub Contracted Raw Material 4"]) + + set_backflush_based_on("Material Transferred for Subcontract") + + order_qty = 250 + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": order_qty, + "rate": 100, + "fg_item": item_code, + "fg_item_qty": order_qty, + }, + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": order_qty, + "rate": 100, + "fg_item": item_code, + "fg_item_qty": order_qty, + }, + ] + + sco = get_subcontracting_order(service_items=service_items) + + # Material receipt entry for the raw materials which will be send to supplier + make_stock_entry( + target="_Test Warehouse - _TC", + item_code="Sub Contracted Raw Material 4", + qty=500, + basic_rate=100, + ) + + rm_items = [ + { + "item_code": item_code, + "rm_item_code": "Sub Contracted Raw Material 4", + "item_name": "_Test Item", + "qty": 250, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos", + "name": sco.supplied_items[0].name, + }, + { + "item_code": item_code, + "rm_item_code": "Sub Contracted Raw Material 4", + "item_name": "_Test Item", + "qty": 250, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos", + }, + ] + + # Raw Materials transfer entry from stores to supplier's warehouse + ste = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items)) + ste.submit() + + # Test sco_rm_detail field has value or not + for item_row in ste.items: + self.assertEqual(item_row.sco_rm_detail, sco.supplied_items[item_row.idx - 1].name) + + sco.load_from_db() + for row in sco.supplied_items: + # Valid that whether transferred quantity is matching with supplied qty or not in the subcontracting order + self.assertEqual(row.supplied_qty, 250.0) + + set_backflush_based_on("BOM") + + +def create_subcontracting_order(**args): + args = frappe._dict(args) + sco = get_mapped_subcontracting_order(source_name=args.po_name) + + for item in sco.items: + item.include_exploded_items = args.get("include_exploded_items", 1) + + if args.get("warehouse"): + for item in sco.items: + item.warehouse = args.warehouse + else: + warehouse = frappe.get_value("Purchase Order", args.po_name, "set_warehouse") + if warehouse: + for item in sco.items: + item.warehouse = warehouse + else: + po = frappe.get_doc("Purchase Order", args.po_name) + warehouses = [] + for item in po.items: + warehouses.append(item.warehouse) + else: + for idx, val in enumerate(sco.items): + val.warehouse = warehouses[idx] + + if not args.do_not_save: + sco.insert() + if not args.do_not_submit: + sco.submit() + + return sco \ No newline at end of file From 785d59876253a6696a60a3724b2f3d3209f80c10 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 9 May 2022 11:28:40 +0530 Subject: [PATCH 17/98] test: SubcontractingReceipt --- .../test_subcontracting_receipt.py | 304 +++++++++++++++++- 1 file changed, 302 insertions(+), 2 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index bc41dca319..8680311c79 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -1,9 +1,309 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe + +import copy + +import frappe from frappe.tests.utils import FrappeTestCase +from frappe.utils import flt + +from erpnext.controllers.tests.test_subcontracting_controller import ( + get_rm_items, + get_subcontracting_order, + make_bom_for_subcontracted_items, + make_raw_materials, + make_service_items, + make_stock_in_entry, + make_stock_transfer_entry, + make_subcontracted_item, + make_subcontracted_items, + set_backflush_based_on, +) +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry +from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import make_subcontracting_receipt class TestSubcontractingReceipt(FrappeTestCase): - pass + def setUp(self): + make_subcontracted_items() + make_raw_materials() + make_service_items() + make_bom_for_subcontracted_items() + + def test_subcontracting(self): + set_backflush_based_on("BOM") + make_stock_entry( + item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100 + ) + make_stock_entry( + item_code="_Test Item Home Desktop 100", + qty=100, + target="_Test Warehouse 1 - _TC", + basic_rate=100, + ) + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 10, + "rate": 100, + "fg_item": "_Test FG Item", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + scr = make_subcontracting_receipt(sco.name) + scr.save() + scr.submit() + rm_supp_cost = sum(item.amount for item in scr.get("supplied_items")) + self.assertEqual(scr.get("items")[0].rm_supp_cost, flt(rm_supp_cost)) + + def test_subcontracting_gle_fg_item_rate_zero(self): + from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries + + set_backflush_based_on("BOM") + make_stock_entry( + item_code="_Test Item", + target="Work In Progress - TCP1", + qty=100, + basic_rate=100, + company="_Test Company with perpetual inventory", + ) + make_stock_entry( + item_code="_Test Item Home Desktop 100", + target="Work In Progress - TCP1", + qty=100, + basic_rate=100, + company="_Test Company with perpetual inventory", + ) + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 10, + "rate": 0, + "fg_item": "_Test FG Item", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + scr = make_subcontracting_receipt(sco.name) + scr.save() + scr.submit() + + gl_entries = get_gl_entries("Subcontracting Receipt", scr.name) + self.assertFalse(gl_entries) + + def test_subcontracting_over_receipt(self): + """ + Behaviour: Raise multiple SCRs against one SCO that in total + receive more than the required qty in the SCO. + Expected Result: Error Raised for Over Receipt against SCO. + """ + from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + make_rm_stock_entry as make_subcontract_transfer_entry, + ) + from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + make_subcontracting_receipt, + ) + from erpnext.subcontracting.doctype.subcontracting_order.test_subcontracting_order import ( + make_subcontracted_item, + ) + + set_backflush_based_on("Material Transferred for Subcontract") + item_code = "_Test Subcontracted FG Item 1" + make_subcontracted_item(item_code=item_code) + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 1, + "rate": 100, + "fg_item": "_Test Subcontracted FG Item 1", + "fg_item_qty": 1, + }, + ] + sco = get_subcontracting_order( + service_items=service_items, + include_exploded_items=0, + ) + + # stock raw materials in a warehouse before transfer + make_stock_entry( + target="_Test Warehouse - _TC", item_code="Test Extra Item 1", qty=10, basic_rate=100 + ) + make_stock_entry( + target="_Test Warehouse - _TC", item_code="_Test FG Item", qty=1, basic_rate=100 + ) + make_stock_entry( + target="_Test Warehouse - _TC", item_code="Test Extra Item 2", qty=1, basic_rate=100 + ) + + rm_items = [ + { + "item_code": item_code, + "rm_item_code": sco.supplied_items[0].rm_item_code, + "item_name": "_Test FG Item", + "qty": sco.supplied_items[0].required_qty, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos", + }, + { + "item_code": item_code, + "rm_item_code": sco.supplied_items[1].rm_item_code, + "item_name": "Test Extra Item 1", + "qty": sco.supplied_items[1].required_qty, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos", + }, + ] + ste = frappe.get_doc(make_subcontract_transfer_entry(sco.name, rm_items)) + ste.to_warehouse = "_Test Warehouse 1 - _TC" + ste.save() + ste.submit() + + scr1 = make_subcontracting_receipt(sco.name) + scr2 = make_subcontracting_receipt(sco.name) + + scr1.submit() + self.assertRaises(frappe.ValidationError, scr2.submit) + + def test_subcontracted_scr_for_multi_transfer_batches(self): + from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + make_rm_stock_entry, + make_subcontracting_receipt, + ) + + set_backflush_based_on("Material Transferred for Subcontract") + item_code = "_Test Subcontracted FG Item 3" + + make_item( + "Sub Contracted Raw Material 3", + {"is_stock_item": 1, "is_sub_contracted_item": 1, "has_batch_no": 1, "create_new_batch": 1}, + ) + + make_subcontracted_item( + item_code=item_code, has_batch_no=1, raw_materials=["Sub Contracted Raw Material 3"] + ) + + order_qty = 500 + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 3", + "qty": order_qty, + "rate": 100, + "fg_item": "_Test Subcontracted FG Item 3", + "fg_item_qty": order_qty, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + + ste1 = make_stock_entry( + target="_Test Warehouse - _TC", + item_code="Sub Contracted Raw Material 3", + qty=300, + basic_rate=100, + ) + ste2 = make_stock_entry( + target="_Test Warehouse - _TC", + item_code="Sub Contracted Raw Material 3", + qty=200, + basic_rate=100, + ) + + transferred_batch = {ste1.items[0].batch_no: 300, ste2.items[0].batch_no: 200} + + rm_items = [ + { + "item_code": item_code, + "rm_item_code": "Sub Contracted Raw Material 3", + "item_name": "_Test Item", + "qty": 300, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos", + "name": sco.supplied_items[0].name, + }, + { + "item_code": item_code, + "rm_item_code": "Sub Contracted Raw Material 3", + "item_name": "_Test Item", + "qty": 200, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos", + "name": sco.supplied_items[0].name, + }, + ] + + se = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items)) + self.assertEqual(len(se.items), 2) + se.items[0].batch_no = ste1.items[0].batch_no + se.items[1].batch_no = ste2.items[0].batch_no + se.submit() + + supplied_qty = frappe.db.get_value( + "Subcontracting Order Supplied Item", + {"parent": sco.name, "rm_item_code": "Sub Contracted Raw Material 3"}, + "supplied_qty", + ) + + self.assertEqual(supplied_qty, 500.00) + + scr = make_subcontracting_receipt(sco.name) + scr.save() + self.assertEqual(len(scr.supplied_items), 2) + + for row in scr.supplied_items: + self.assertEqual(transferred_batch.get(row.batch_no), row.consumed_qty) + + +def get_items(**args): + args = frappe._dict(args) + return [ + { + "conversion_factor": 1.0, + "description": "_Test Item", + "doctype": "Subcontracting Receipt Item", + "item_code": "_Test Item", + "item_name": "_Test Item", + "parentfield": "items", + "qty": 5.0, + "rate": 50.0, + "received_qty": 5.0, + "rejected_qty": 0.0, + "stock_uom": "_Test UOM", + "warehouse": args.warehouse or "_Test Warehouse - _TC", + "cost_center": args.cost_center or "Main - _TC", + }, + { + "conversion_factor": 1.0, + "description": "_Test Item Home Desktop 100", + "doctype": "Subcontracting Receipt Item", + "item_code": "_Test Item Home Desktop 100", + "item_name": "_Test Item Home Desktop 100", + "parentfield": "items", + "qty": 5.0, + "rate": 50.0, + "received_qty": 5.0, + "rejected_qty": 0.0, + "stock_uom": "_Test UOM", + "warehouse": args.warehouse or "_Test Warehouse 1 - _TC", + "cost_center": args.cost_center or "Main - _TC", + }, + ] From dafaed3cbd6f7d52119e2600c675aa5fa64b04ba Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 4 May 2022 15:20:29 +0530 Subject: [PATCH 18/98] refactor!: Purchase Order --- .../doctype/purchase_order/purchase_order.js | 206 +------- .../doctype/purchase_order/purchase_order.py | 150 ------ .../purchase_order/test_purchase_order.py | 452 +----------------- .../doctype/purchase_order/test_records.json | 34 -- .../purchase_order_item.json | 2 - 5 files changed, 2 insertions(+), 842 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 2cad1fb081..d347026b63 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -7,17 +7,6 @@ frappe.provide("erpnext.accounts.dimensions"); frappe.ui.form.on("Purchase Order", { setup: function(frm) { - - frm.set_query("reserve_warehouse", "supplied_items", function() { - return { - filters: { - "company": frm.doc.company, - "name": ['!=', frm.doc.supplier_warehouse], - "is_group": 0 - } - } - }); - frm.set_indicator_formatter('item_code', function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" }) @@ -59,39 +48,6 @@ frappe.ui.form.on("Purchase Order", { frm.set_value("tax_withholding_category", frm.supplier_tds); } }, - - refresh: function(frm) { - frm.trigger('get_materials_from_supplier'); - }, - - get_materials_from_supplier: function(frm) { - let po_details = []; - - if (frm.doc.supplied_items && (frm.doc.per_received == 100 || frm.doc.status === 'Closed')) { - frm.doc.supplied_items.forEach(d => { - if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) { - po_details.push(d.name) - } - }); - } - - if (po_details && po_details.length) { - frm.add_custom_button(__('Return of Components'), () => { - frm.call({ - method: 'erpnext.buying.doctype.purchase_order.purchase_order.get_materials_from_supplier', - freeze: true, - freeze_message: __('Creating Stock Entry'), - args: { purchase_order: frm.doc.name, po_details: po_details }, - callback: function(r) { - if (r && r.message) { - const doc = frappe.model.sync(r.message); - frappe.set_route("Form", doc[0].doctype, doc[0].name); - } - } - }); - }, __('Create')); - } - } }); frappe.ui.form.on("Purchase Order Item", { @@ -112,13 +68,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e this.frm.custom_make_buttons = { 'Purchase Receipt': 'Purchase Receipt', 'Purchase Invoice': 'Purchase Invoice', - 'Stock Entry': 'Material to Supplier', 'Payment Entry': 'Payment', 'Subcontracting Order': 'Subcontracting Order' } super.setup(); - } refresh(doc, cdt, cdn) { @@ -185,10 +139,6 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e if (doc.status != "On Hold") { if(flt(doc.per_received) < 100 && allow_receipt) { cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create')); - if(doc.is_subcontracted && me.has_unsupplied_items()) { - cur_frm.add_custom_button(__('Material to Supplier'), - function() { me.make_stock_entry(); }, __("Transfer")); - } if (doc.is_subcontracted) { cur_frm.add_custom_button(__('Subcontracting Order'), this.make_subcontracting_order, __('Create')); } @@ -258,142 +208,6 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e set_schedule_date(this.frm); } - has_unsupplied_items() { - return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty); - } - - make_stock_entry() { - var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; }); - var me = this; - - if(items.length >= 1){ - me.raw_material_data = []; - me.show_dialog = 1; - let title = __('Transfer Material to Supplier'); - let fields = [ - {fieldtype:'Section Break', label: __('Raw Materials')}, - {fieldname: 'sub_con_rm_items', fieldtype: 'Table', label: __('Items'), - fields: [ - { - fieldtype:'Data', - fieldname:'item_code', - label: __('Item'), - read_only:1, - in_list_view:1 - }, - { - fieldtype:'Data', - fieldname:'rm_item_code', - label: __('Raw Material'), - read_only:1, - in_list_view:1 - }, - { - fieldtype:'Float', - read_only:1, - fieldname:'qty', - label: __('Quantity'), - read_only:1, - in_list_view:1 - }, - { - fieldtype:'Data', - read_only:1, - fieldname:'warehouse', - label: __('Reserve Warehouse'), - in_list_view:1 - }, - { - fieldtype:'Float', - read_only:1, - fieldname:'rate', - label: __('Rate'), - hidden:1 - }, - { - fieldtype:'Float', - read_only:1, - fieldname:'amount', - label: __('Amount'), - hidden:1 - }, - { - fieldtype:'Link', - read_only:1, - fieldname:'uom', - label: __('UOM'), - hidden:1 - } - ], - data: me.raw_material_data, - get_data: function() { - return me.raw_material_data; - } - } - ] - - me.dialog = new frappe.ui.Dialog({ - title: title, fields: fields - }); - - if (me.frm.doc['supplied_items']) { - me.frm.doc['supplied_items'].forEach((item, index) => { - if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) { - me.raw_material_data.push ({ - 'name':item.name, - 'item_code': item.main_item_code, - 'rm_item_code': item.rm_item_code, - 'item_name': item.rm_item_code, - 'qty': item.required_qty - item.supplied_qty, - 'warehouse':item.reserve_warehouse, - 'rate':item.rate, - 'amount':item.amount, - 'stock_uom':item.stock_uom - }); - me.dialog.fields_dict.sub_con_rm_items.grid.refresh(); - } - }) - } - - me.dialog.get_field('sub_con_rm_items').check_all_rows() - - me.dialog.show() - this.dialog.set_primary_action(__('Transfer'), function() { - me.values = me.dialog.get_values(); - if(me.values) { - me.values.sub_con_rm_items.map((row,i) => { - if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) { - let row_id = i+1; - frappe.throw(__("Item Code, warehouse and quantity are required on row {0}", [row_id])); - } - }) - me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children()) - me.dialog.hide() - } - }); - } - - me.dialog.get_close_btn().on('click', () => { - me.dialog.hide(); - }); - - } - - _make_rm_stock_entry(rm_items) { - frappe.call({ - method:"erpnext.buying.doctype.purchase_order.purchase_order.make_rm_stock_entry", - args: { - purchase_order: cur_frm.doc.name, - rm_items: rm_items - } - , - callback: function(r) { - var doclist = frappe.model.sync(r.message); - frappe.set_route("Form", doclist[0].doctype, doclist[0].name); - } - }); - } - make_inter_company_order(frm) { frappe.model.open_mapped_doc({ method: "erpnext.buying.doctype.purchase_order.purchase_order.make_inter_company_sales_order", @@ -632,28 +446,10 @@ cur_frm.fields_dict['items'].grid.get_field('project').get_query = function(doc, } } -cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt, cdn) { - var d = locals[cdt][cdn] - return { - filters: [ - ['BOM', 'item', '=', d.item_code], - ['BOM', 'is_active', '=', '1'], - ['BOM', 'docstatus', '=', '1'], - ['BOM', 'company', '=', doc.company] - ] - } -} - function set_schedule_date(frm) { if(frm.doc.schedule_date){ erpnext.utils.copy_value_in_all_rows(frm.doc, frm.doc.doctype, frm.doc.name, "items", "schedule_date"); } } -frappe.provide("erpnext.buying"); - -frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) { - if (frm.doc.is_subcontracted) { - erpnext.buying.get_default_bom(frm); - } -}); +frappe.provide("erpnext.buying"); \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 1945079171..234bec17ef 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -24,7 +24,6 @@ from erpnext.controllers.buying_controller import BuyingController from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.stock.doctype.item.item import get_item_defaults, get_last_purchase_details from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty -from erpnext.stock.utils import get_bin form_grid_templates = {"items": "templates/form_grid/item_grid.html"} @@ -70,7 +69,6 @@ class PurchaseOrder(BuyingController): self.validate_for_subcontracting() self.validate_minimum_order_qty() self.validate_fg_item_for_subcontracting() - self.create_raw_materials_supplied("supplied_items") self.set_received_qty_for_drop_ship_items() validate_inter_company_party( self.doctype, self.supplier, self.company, self.inter_company_order_reference @@ -307,9 +305,6 @@ class PurchaseOrder(BuyingController): self.set_status(update=True, status=status) self.update_requested_qty() self.update_ordered_qty() - if self.is_subcontracted: - self.update_reserved_qty_for_subcontract() - self.notify_update() clear_doctype_notifications(self) @@ -324,9 +319,6 @@ class PurchaseOrder(BuyingController): self.update_ordered_qty() self.validate_budget() - if self.is_subcontracted: - self.update_reserved_qty_for_subcontract() - frappe.get_doc("Authorization Control").validate_approving_authority( self.doctype, self.company, self.base_grand_total ) @@ -344,9 +336,6 @@ class PurchaseOrder(BuyingController): if self.has_drop_ship_item(): self.update_delivered_qty_in_sales_order() - if self.is_subcontracted: - self.update_reserved_qty_for_subcontract() - self.check_on_hold_or_closed_status() frappe.db.set(self, "status", "Cancelled") @@ -416,12 +405,6 @@ class PurchaseOrder(BuyingController): if item.delivered_by_supplier == 1: item.received_qty = item.qty - def update_reserved_qty_for_subcontract(self): - for d in self.supplied_items: - if d.rm_item_code: - stock_bin = get_bin(d.rm_item_code, d.reserve_warehouse) - stock_bin.update_reserved_qty_for_sub_contracting() - def update_receiving_percentage(self): total_qty, received_qty = 0.0, 0.0 for item in self.items: @@ -599,78 +582,6 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions return doc -@frappe.whitelist() -def make_rm_stock_entry(purchase_order, rm_items): - rm_items_list = rm_items - - if isinstance(rm_items, str): - rm_items_list = json.loads(rm_items) - elif not rm_items: - frappe.throw(_("No Items available for transfer")) - - if rm_items_list: - fg_items = list(set(d["item_code"] for d in rm_items_list)) - else: - frappe.throw(_("No Items selected for transfer")) - - if purchase_order: - purchase_order = frappe.get_doc("Purchase Order", purchase_order) - - if fg_items: - items = tuple(set(d["rm_item_code"] for d in rm_items_list)) - item_wh = get_item_details(items) - - stock_entry = frappe.new_doc("Stock Entry") - stock_entry.purpose = "Send to Subcontractor" - stock_entry.purchase_order = purchase_order.name - stock_entry.supplier = purchase_order.supplier - stock_entry.supplier_name = purchase_order.supplier_name - stock_entry.supplier_address = purchase_order.supplier_address - stock_entry.address_display = purchase_order.address_display - stock_entry.company = purchase_order.company - stock_entry.to_warehouse = purchase_order.supplier_warehouse - stock_entry.set_stock_entry_type() - - for item_code in fg_items: - for rm_item_data in rm_items_list: - if rm_item_data["item_code"] == item_code: - rm_item_code = rm_item_data["rm_item_code"] - items_dict = { - rm_item_code: { - "po_detail": rm_item_data.get("name"), - "item_name": rm_item_data["item_name"], - "description": item_wh.get(rm_item_code, {}).get("description", ""), - "qty": rm_item_data["qty"], - "from_warehouse": rm_item_data["warehouse"], - "stock_uom": rm_item_data["stock_uom"], - "serial_no": rm_item_data.get("serial_no"), - "batch_no": rm_item_data.get("batch_no"), - "main_item_code": rm_item_data["item_code"], - "allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"), - } - } - stock_entry.add_to_stock_entry_detail(items_dict) - return stock_entry.as_dict() - else: - frappe.throw(_("No Items selected for transfer")) - return purchase_order.name - - -def get_item_details(items): - item_details = {} - for d in frappe.db.sql( - """select item_code, description, allow_alternative_item from `tabItem` - where name in ({0})""".format( - ", ".join(["%s"] * len(items)) - ), - items, - as_dict=1, - ): - item_details[d.item_code] = d - - return item_details - - def get_list_context(context=None): from erpnext.controllers.website_list_for_contact import get_list_context @@ -700,67 +611,6 @@ def make_inter_company_sales_order(source_name, target_doc=None): return make_inter_company_transaction("Purchase Order", source_name, target_doc) -@frappe.whitelist() -def get_materials_from_supplier(purchase_order, po_details): - if isinstance(po_details, str): - po_details = json.loads(po_details) - - doc = frappe.get_cached_doc("Purchase Order", purchase_order) - doc.initialized_fields() - doc.purchase_orders = [doc.name] - doc.get_available_materials() - - if not doc.available_materials: - frappe.throw( - _("Materials are already received against the purchase order {0}").format(purchase_order) - ) - - return make_return_stock_entry_for_subcontract(doc.available_materials, doc, po_details) - - -def make_return_stock_entry_for_subcontract(available_materials, po_doc, po_details): - ste_doc = frappe.new_doc("Stock Entry") - ste_doc.purpose = "Material Transfer" - ste_doc.purchase_order = po_doc.name - ste_doc.company = po_doc.company - ste_doc.is_return = 1 - - for key, value in available_materials.items(): - if not value.qty: - continue - - if value.batch_no: - for batch_no, qty in value.batch_no.items(): - if qty > 0: - add_items_in_ste(ste_doc, value, value.qty, po_details, batch_no) - else: - add_items_in_ste(ste_doc, value, value.qty, po_details) - - ste_doc.set_stock_entry_type() - ste_doc.calculate_rate_and_amount() - - return ste_doc - - -def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None): - item = ste_doc.append("items", row.item_details) - - po_detail = list(set(row.po_details).intersection(po_details)) - item.update( - { - "qty": qty, - "batch_no": batch_no, - "basic_rate": row.item_details["rate"], - "po_detail": po_detail[0] if po_detail else "", - "s_warehouse": row.item_details["t_warehouse"], - "t_warehouse": row.item_details["s_warehouse"], - "item_code": row.item_details["rm_item_code"], - "subcontracted_item": row.item_details["main_item_code"], - "serial_no": "\n".join(row.serial_no) if row.serial_no else "", - } - ) - - @frappe.whitelist() def make_subcontracting_order(source_name, target_doc=None): return get_mapped_subcontracting_order(source_name, target_doc) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 1a7f2dd5d9..e1da54e410 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -13,9 +13,6 @@ from erpnext.buying.doctype.purchase_order.purchase_order import ( make_purchase_invoice as make_pi_from_po, ) from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt -from erpnext.buying.doctype.purchase_order.purchase_order import ( - make_rm_stock_entry as make_subcontract_transfer_entry, -) from erpnext.controllers.accounts_controller import update_child_qty_rate from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order from erpnext.stock.doctype.item.test_item import make_item @@ -24,7 +21,6 @@ from erpnext.stock.doctype.material_request.test_material_request import make_ma from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as make_pi_from_pr, ) -from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry class TestPurchaseOrder(FrappeTestCase): @@ -389,31 +385,6 @@ class TestPurchaseOrder(FrappeTestCase): new_item_with_tax.delete() frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete() - def test_update_child_uom_conv_factor_change(self): - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) - total_reqd_qty = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")]) - - trans_item = json.dumps( - [ - { - "item_code": po.get("items")[0].item_code, - "rate": po.get("items")[0].rate, - "qty": po.get("items")[0].qty, - "uom": "_Test UOM 1", - "conversion_factor": 2, - "docname": po.get("items")[0].name, - } - ] - ) - update_child_qty_rate("Purchase Order", trans_item, po.name) - po.reload() - - total_reqd_qty_after_change = sum( - d.get("required_qty") for d in po.as_dict().get("supplied_items") - ) - - self.assertEqual(total_reqd_qty_after_change, 2 * total_reqd_qty) - def test_update_qty(self): po = create_purchase_order() @@ -572,10 +543,6 @@ class TestPurchaseOrder(FrappeTestCase): ) automatically_fetch_payment_terms(enable=0) - def test_subcontracting(self): - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) - self.assertEqual(len(po.get("supplied_items")), 2) - def test_warehouse_company_validation(self): from erpnext.stock.utils import InvalidWarehouseCompany @@ -740,379 +707,6 @@ class TestPurchaseOrder(FrappeTestCase): pi.insert() self.assertTrue(pi.get("payment_schedule")) - def test_reserved_qty_subcontract_po(self): - # Make stock available for raw materials - make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) - make_stock_entry( - target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100 - ) - make_stock_entry( - target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=30, basic_rate=100 - ) - make_stock_entry( - target="_Test Warehouse 1 - _TC", - item_code="_Test Item Home Desktop 100", - qty=30, - basic_rate=100, - ) - - bin1 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], - as_dict=1, - ) - - # Submit PO - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) - - bin2 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], - as_dict=1, - ) - - self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) - self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10) - self.assertNotEqual(bin1.modified, bin2.modified) - - # Create stock transfer - rm_item = [ - { - "item_code": "_Test FG Item", - "rm_item_code": "_Test Item", - "item_name": "_Test Item", - "qty": 6, - "warehouse": "_Test Warehouse - _TC", - "rate": 100, - "amount": 600, - "stock_uom": "Nos", - } - ] - rm_item_string = json.dumps(rm_item) - se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string)) - se.to_warehouse = "_Test Warehouse 1 - _TC" - se.save() - se.submit() - - bin3 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname="reserved_qty_for_sub_contract", - as_dict=1, - ) - - self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) - - # close PO - po.update_status("Closed") - bin4 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname="reserved_qty_for_sub_contract", - as_dict=1, - ) - - self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) - - # Re-open PO - po.update_status("Submitted") - bin5 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname="reserved_qty_for_sub_contract", - as_dict=1, - ) - - self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) - - make_stock_entry( - target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=40, basic_rate=100 - ) - make_stock_entry( - target="_Test Warehouse 1 - _TC", - item_code="_Test Item Home Desktop 100", - qty=40, - basic_rate=100, - ) - - # make Purchase Receipt against PO - pr = make_purchase_receipt(po.name) - pr.supplier_warehouse = "_Test Warehouse 1 - _TC" - pr.save() - pr.submit() - - bin6 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname="reserved_qty_for_sub_contract", - as_dict=1, - ) - - self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) - - # Cancel PR - pr.cancel() - bin7 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname="reserved_qty_for_sub_contract", - as_dict=1, - ) - - self.assertEqual(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) - - # Make Purchase Invoice - pi = make_pi_from_po(po.name) - pi.update_stock = 1 - pi.supplier_warehouse = "_Test Warehouse 1 - _TC" - pi.insert() - pi.submit() - bin8 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname="reserved_qty_for_sub_contract", - as_dict=1, - ) - - self.assertEqual(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) - - # Cancel PR - pi.cancel() - bin9 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname="reserved_qty_for_sub_contract", - as_dict=1, - ) - - self.assertEqual(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) - - # Cancel Stock Entry - se.cancel() - bin10 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname="reserved_qty_for_sub_contract", - as_dict=1, - ) - - self.assertEqual(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) - - # Cancel PO - po.reload() - po.cancel() - bin11 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname="reserved_qty_for_sub_contract", - as_dict=1, - ) - - self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) - - def test_exploded_items_in_subcontracted(self): - item_code = "_Test Subcontracted FG Item 11" - make_subcontracted_item(item_code=item_code) - - po = create_purchase_order( - item_code=item_code, - qty=1, - is_subcontracted=1, - supplier_warehouse="_Test Warehouse 1 - _TC", - include_exploded_items=1, - ) - - name = frappe.db.get_value("BOM", {"item": item_code}, "name") - bom = frappe.get_doc("BOM", name) - - exploded_items = sorted( - [d.item_code for d in bom.exploded_items if not d.get("sourced_by_supplier")] - ) - supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) - self.assertEqual(exploded_items, supplied_items) - - po1 = create_purchase_order( - item_code=item_code, - qty=1, - is_subcontracted=1, - supplier_warehouse="_Test Warehouse 1 - _TC", - include_exploded_items=0, - ) - - supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items]) - bom_items = sorted([d.item_code for d in bom.items if not d.get("sourced_by_supplier")]) - - self.assertEqual(supplied_items1, bom_items) - - def test_backflush_based_on_stock_entry(self): - item_code = "_Test Subcontracted FG Item 1" - make_subcontracted_item(item_code=item_code) - make_item("Sub Contracted Raw Material 1", {"is_stock_item": 1, "is_sub_contracted_item": 1}) - - update_backflush_based_on("Material Transferred for Subcontract") - - order_qty = 5 - po = create_purchase_order( - item_code=item_code, - qty=order_qty, - is_subcontracted=1, - supplier_warehouse="_Test Warehouse 1 - _TC", - ) - - make_stock_entry( - target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100 - ) - make_stock_entry( - target="_Test Warehouse - _TC", item_code="Test Extra Item 1", qty=100, basic_rate=100 - ) - make_stock_entry( - target="_Test Warehouse - _TC", item_code="Test Extra Item 2", qty=10, basic_rate=100 - ) - make_stock_entry( - target="_Test Warehouse - _TC", - item_code="Sub Contracted Raw Material 1", - qty=10, - basic_rate=100, - ) - - rm_items = [ - { - "item_code": item_code, - "rm_item_code": "Sub Contracted Raw Material 1", - "item_name": "_Test Item", - "qty": 10, - "warehouse": "_Test Warehouse - _TC", - "stock_uom": "Nos", - }, - { - "item_code": item_code, - "rm_item_code": "_Test Item Home Desktop 100", - "item_name": "_Test Item Home Desktop 100", - "qty": 20, - "warehouse": "_Test Warehouse - _TC", - "stock_uom": "Nos", - }, - { - "item_code": item_code, - "rm_item_code": "Test Extra Item 1", - "item_name": "Test Extra Item 1", - "qty": 10, - "warehouse": "_Test Warehouse - _TC", - "stock_uom": "Nos", - }, - { - "item_code": item_code, - "rm_item_code": "Test Extra Item 2", - "stock_uom": "Nos", - "qty": 10, - "warehouse": "_Test Warehouse - _TC", - "item_name": "Test Extra Item 2", - }, - ] - - rm_item_string = json.dumps(rm_items) - se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string)) - se.submit() - - pr = make_purchase_receipt(po.name) - - received_qty = 2 - # partial receipt - pr.get("items")[0].qty = received_qty - pr.save() - pr.submit() - - transferred_items = sorted( - [d.item_code for d in se.get("items") if se.purchase_order == po.name] - ) - issued_items = sorted([d.rm_item_code for d in pr.get("supplied_items")]) - - self.assertEqual(transferred_items, issued_items) - self.assertEqual(pr.get("items")[0].rm_supp_cost, 2000) - - transferred_rm_map = frappe._dict() - for item in rm_items: - transferred_rm_map[item.get("rm_item_code")] = item - - update_backflush_based_on("BOM") - - def test_supplied_qty_against_subcontracted_po(self): - item_code = "_Test Subcontracted FG Item 5" - make_item("Sub Contracted Raw Material 4", {"is_stock_item": 1, "is_sub_contracted_item": 1}) - - make_subcontracted_item(item_code=item_code, raw_materials=["Sub Contracted Raw Material 4"]) - - update_backflush_based_on("Material Transferred for Subcontract") - - order_qty = 250 - po = create_purchase_order( - item_code=item_code, - qty=order_qty, - is_subcontracted=1, - supplier_warehouse="_Test Warehouse 1 - _TC", - do_not_save=True, - ) - - # Add same subcontracted items multiple times - po.append( - "items", - { - "item_code": item_code, - "qty": order_qty, - "schedule_date": add_days(nowdate(), 1), - "warehouse": "_Test Warehouse - _TC", - }, - ) - - po.set_missing_values() - po.submit() - - # Material receipt entry for the raw materials which will be send to supplier - make_stock_entry( - target="_Test Warehouse - _TC", - item_code="Sub Contracted Raw Material 4", - qty=500, - basic_rate=100, - ) - - rm_items = [ - { - "item_code": item_code, - "rm_item_code": "Sub Contracted Raw Material 4", - "item_name": "_Test Item", - "qty": 250, - "warehouse": "_Test Warehouse - _TC", - "stock_uom": "Nos", - "name": po.supplied_items[0].name, - }, - { - "item_code": item_code, - "rm_item_code": "Sub Contracted Raw Material 4", - "item_name": "_Test Item", - "qty": 250, - "warehouse": "_Test Warehouse - _TC", - "stock_uom": "Nos", - }, - ] - - # Raw Materials transfer entry from stores to supplier's warehouse - rm_item_string = json.dumps(rm_items) - se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string)) - se.submit() - - # Test po_detail field has value or not - for item_row in se.items: - self.assertEqual(item_row.po_detail, po.supplied_items[item_row.idx - 1].name) - - po_doc = frappe.get_doc("Purchase Order", po.name) - for row in po_doc.supplied_items: - # Valid that whether transferred quantity is matching with supplied qty or not in the purchase order - self.assertEqual(row.supplied_qty, 250.0) - - update_backflush_based_on("BOM") - def test_advance_payment_entry_unlink_against_purchase_order(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry @@ -1211,50 +805,6 @@ def make_pr_against_po(po, received_qty=0): return pr -def make_subcontracted_item(**args): - from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom - - args = frappe._dict(args) - - if not frappe.db.exists("Item", args.item_code): - make_item( - args.item_code, - { - "is_stock_item": 1, - "is_sub_contracted_item": 1, - "has_batch_no": args.get("has_batch_no") or 0, - }, - ) - - if not args.raw_materials: - if not frappe.db.exists("Item", "Test Extra Item 1"): - make_item( - "Test Extra Item 1", - { - "is_stock_item": 1, - }, - ) - - if not frappe.db.exists("Item", "Test Extra Item 2"): - make_item( - "Test Extra Item 2", - { - "is_stock_item": 1, - }, - ) - - args.raw_materials = ["_Test FG Item", "Test Extra Item 1"] - - if not frappe.db.get_value("BOM", {"item": args.item_code}, "name"): - make_bom(item=args.item_code, raw_materials=args.get("raw_materials")) - - -def update_backflush_based_on(based_on): - doc = frappe.get_doc("Buying Settings") - doc.backflush_raw_materials_of_subcontract_based_on = based_on - doc.save() - - def get_same_items(): return [ { @@ -1341,4 +891,4 @@ def get_requested_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC") test_dependencies = ["BOM", "Item Price"] -test_records = frappe.get_test_records("Purchase Order") +test_records = frappe.get_test_records("Purchase Order") \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/test_records.json b/erpnext/buying/doctype/purchase_order/test_records.json index 896050ce43..4df994a68c 100644 --- a/erpnext/buying/doctype/purchase_order/test_records.json +++ b/erpnext/buying/doctype/purchase_order/test_records.json @@ -1,38 +1,4 @@ [ - { - "advance_paid": 0.0, - "buying_price_list": "_Test Price List", - "company": "_Test Company", - "conversion_rate": 1.0, - "currency": "INR", - "doctype": "Purchase Order", - "base_grand_total": 5000.0, - "grand_total": 5000.0, - "is_subcontracted": 1, - "naming_series": "_T-Purchase Order-", - "base_net_total": 5000.0, - "items": [ - { - "base_amount": 5000.0, - "conversion_factor": 1.0, - "description": "_Test FG Item", - "doctype": "Purchase Order Item", - "item_code": "_Test FG Item", - "item_name": "_Test FG Item", - "parentfield": "items", - "qty": 10.0, - "rate": 500.0, - "schedule_date": "2013-03-01", - "stock_uom": "_Test UOM", - "uom": "_Test UOM", - "warehouse": "_Test Warehouse - _TC" - } - ], - "supplier": "_Test Supplier", - "supplier_name": "_Test Supplier", - "transaction_date": "2013-02-12", - "schedule_date": "2013-02-13" - }, { "advance_paid": 0.0, "buying_price_list": "_Test Price List", diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 7f797cfd2f..12eef79dff 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -574,7 +574,6 @@ "read_only": 1 }, { - "depends_on": "eval:parent.is_subcontracted", "fieldname": "bom", "fieldtype": "Link", "label": "BOM", @@ -584,7 +583,6 @@ }, { "default": "0", - "depends_on": "eval:parent.is_subcontracted", "fieldname": "include_exploded_items", "fieldtype": "Check", "hidden": 1, From 34bda14b5b0707704dfd724a95b51e1e8b2bf2af Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 4 May 2022 15:21:31 +0530 Subject: [PATCH 19/98] refactor!: Buying Controller --- erpnext/controllers/buying_controller.py | 91 +----------------------- 1 file changed, 3 insertions(+), 88 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 6fdb002be0..398154e31b 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -12,7 +12,6 @@ from erpnext.accounts.party import get_party_details from erpnext.buying.utils import update_last_purchase_rate, validate_for_items from erpnext.controllers.sales_and_purchase_return import get_rate_for_return from erpnext.controllers.stock_controller import StockController -from erpnext.controllers.subcontracting import Subcontracting from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.utils import get_incoming_rate @@ -21,7 +20,7 @@ class QtyMismatchError(ValidationError): pass -class BuyingController(StockController, Subcontracting): +class BuyingController(StockController): def get_feed(self): if self.get("supplier_name"): return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total) @@ -52,7 +51,6 @@ class BuyingController(StockController, Subcontracting): # sub-contracting self.validate_for_subcontracting() - self.create_raw_materials_supplied("supplied_items") self.set_landed_cost_voucher_amount() if self.doctype in ("Purchase Receipt", "Purchase Invoice"): @@ -253,11 +251,9 @@ class BuyingController(StockController, Subcontracting): ) qty_in_stock_uom = flt(item.qty * item.conversion_factor) - item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate) item.valuation_rate = ( item.base_net_amount + item.item_tax_amount - + item.rm_supp_cost + flt(item.landed_cost_voucher_amount) ) / qty_in_stock_uom else: @@ -313,76 +309,15 @@ class BuyingController(StockController, Subcontracting): alert=1, ) - def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True): - supplied_items_cost = 0.0 - for d in self.get("supplied_items"): - if d.reference_name == item_row_id: - if reset_outgoing_rate and frappe.get_cached_value("Item", d.rm_item_code, "is_stock_item"): - rate = get_incoming_rate( - { - "item_code": d.rm_item_code, - "warehouse": self.supplier_warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, - "qty": -1 * d.consumed_qty, - "serial_no": d.serial_no, - "batch_no": d.batch_no, - } - ) - - if rate > 0: - d.rate = rate - - d.amount = flt(flt(d.consumed_qty) * flt(d.rate), d.precision("amount")) - supplied_items_cost += flt(d.amount) - - return supplied_items_cost - def validate_for_subcontracting(self): if self.is_subcontracted: 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)) - - for item in self.get("items"): - 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)) - - if self.doctype != "Purchase Order": - return - - for row in self.get("supplied_items"): - 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(_(msg)) else: for item in self.get("items"): - if item.bom: + if item.get("bom"): item.bom = None - def create_raw_materials_supplied(self, raw_material_table): - if self.is_subcontracted: - self.set_materials_for_subcontracted_items(raw_material_table) - - elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]: - for item in self.get("items"): - item.rm_supp_cost = 0.0 - - if not self.is_subcontracted and self.get("supplied_items"): - self.set("supplied_items", []) - - @property - def sub_contracted_items(self): - if not hasattr(self, "_sub_contracted_items"): - self._sub_contracted_items = [] - item_codes = list(set(item.item_code for item in self.get("items"))) - if item_codes: - items = frappe.get_all( - "Item", filters={"name": ["in", item_codes], "is_sub_contracted_item": 1} - ) - self._sub_contracted_items = [item.name for item in items] - - return self._sub_contracted_items - def set_qty_as_per_stock_uom(self): for d in self.get("items"): if d.meta.get_field("stock_qty"): @@ -502,7 +437,7 @@ class BuyingController(StockController, Subcontracting): sle.update( { "incoming_rate": incoming_rate, - "recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0, + "recalculate_rate": 1 if (self.is_subcontracted and d.fg_item) or d.from_warehouse else 0, } ) sl_entries.append(sle) @@ -530,7 +465,6 @@ class BuyingController(StockController, Subcontracting): ) ) - self.make_sl_entries_for_supplier_warehouse(sl_entries) self.make_sl_entries( sl_entries, allow_negative_stock=allow_negative_stock, @@ -557,25 +491,6 @@ class BuyingController(StockController, Subcontracting): ) po_obj.update_ordered_qty(po_item_rows) - if self.is_subcontracted: - po_obj.update_reserved_qty_for_subcontract() - - def make_sl_entries_for_supplier_warehouse(self, sl_entries): - if hasattr(self, "supplied_items"): - for d in self.get("supplied_items"): - # negative quantity is passed, as raw material qty has to be decreased - # when PR is submitted and it has to be increased when PR is cancelled - sl_entries.append( - self.get_sl_entries( - d, - { - "item_code": d.rm_item_code, - "warehouse": self.supplier_warehouse, - "actual_qty": -1 * flt(d.consumed_qty), - "dependant_sle_voucher_detail_no": d.reference_name, - }, - ) - ) def on_submit(self): if self.get("is_return"): From 73484448f2e479cc469db23a6490a2b05dd42760 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 6 May 2022 10:48:34 +0530 Subject: [PATCH 20/98] refactor!: Stock Entry --- .../stock/doctype/stock_entry/stock_entry.js | 21 ++- .../doctype/stock_entry/stock_entry.json | 10 +- .../stock/doctype/stock_entry/stock_entry.py | 129 ++++++++++++------ 3 files changed, 106 insertions(+), 54 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index a94087821a..dc4d245d1a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -611,7 +611,21 @@ frappe.ui.form.on('Stock Entry', { apply_putaway_rule: function (frm) { if (frm.doc.apply_putaway_rule) erpnext.apply_putaway_rule(frm, frm.doc.purpose); - } + }, + + subcontracting_order: (frm) => { + if (frm.doc.subcontracting_order) { + erpnext.utils.map_current_doc({ + method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order', + source_name: frm.doc.subcontracting_order, + target_doc: frm, + freeze: true, + }); + } + else { + frm.set_value("items", []); + } + }, }); frappe.ui.form.on('Stock Entry Detail', { @@ -789,11 +803,10 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle return erpnext.queries.item({is_stock_item: 1}); }; - this.frm.set_query("purchase_order", function() { + this.frm.set_query("subcontracting_order", function() { return { "filters": { "docstatus": 1, - "is_subcontracted": 1, "company": me.frm.doc.company } }; @@ -814,7 +827,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle } } - this.frm.add_fetch("purchase_order", "supplier", "supplier"); + this.frm.add_fetch("subcontracting_order", "supplier", "supplier"); frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'supplier', doctype: 'Supplier' } this.frm.set_query("supplier_address", erpnext.queries.address_query) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 7b9eccd4aa..db505eabf2 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -148,11 +148,11 @@ "search_index": 1 }, { - "depends_on": "eval:doc.purpose==\"Send to Subcontractor\"", - "fieldname": "purchase_order", - "fieldtype": "Link", - "label": "Purchase Order", - "options": "Purchase Order" + "fieldname": "purchase_order", + "fieldtype": "Link", + "label": "Purchase Order", + "options": "Purchase Order", + "read_only": 1 }, { "depends_on": "eval:doc.purpose==\"Send to Subcontractor\"", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 5adb8b273e..ac9ce8718a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -867,15 +867,19 @@ class StockEntry(StockController): se_item.item_code, self.subcontracting_order ) ) - total_supplied = frappe.db.sql( - """select sum(transfer_qty) - from `tabStock Entry Detail`, `tabStock Entry` - where `tabStock Entry`.subcontracting_order = %s - and `tabStock Entry`.docstatus = 1 - and `tabStock Entry Detail`.item_code = %s - and `tabStock Entry Detail`.parent = `tabStock Entry`.name""", - (self.subcontracting_order, se_item.item_code), - )[0][0] + + parent = frappe.qb.DocType("Stock Entry") + child = frappe.qb.DocType("Stock Entry Detail") + + total_supplied = ( + frappe.qb.from_(parent) + .inner_join(child) + .on(parent.name == child.parent) + .select(Sum(child.transfer_qty)) + .where(parent.docstatus == 1) + .where(parent.subcontracting_order == self.subcontracting_order) + .where(child.item_code == se_item.item_code) + ).run()[0][0] if flt(total_supplied, precision) > flt(total_allowed, precision): frappe.throw( @@ -1261,11 +1265,13 @@ class StockEntry(StockController): args.batch_no = get_batch_no(args["item_code"], args["s_warehouse"], args["qty"]) if ( - self.purpose == "Send to Subcontractor" and self.get("purchase_order") and args.get("item_code") + self.purpose == "Send to Subcontractor" + and self.get("subcontracting_order") + and args.get("item_code") ): subcontract_items = frappe.get_all( - "Purchase Order Item Supplied", - {"parent": self.purchase_order, "rm_item_code": args.get("item_code")}, + "Subcontracting Order Supplied Item", + {"parent": self.subcontracting_order, "rm_item_code": args.get("item_code")}, "main_item_code", ) @@ -1359,27 +1365,27 @@ class StockEntry(StockController): item_dict = self.get_bom_raw_materials(self.fg_completed_qty) - # Get PO Supplied Items Details - if self.purchase_order and self.purpose == "Send to Subcontractor": - # Get PO Supplied Items Details - item_wh = frappe._dict( - frappe.db.sql( - """ - SELECT - rm_item_code, reserve_warehouse - FROM - `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup - WHERE - po.name = poitemsup.parent and po.name = %s """, - self.purchase_order, - ) - ) + # Get SCO Supplied Items Details + if self.subcontracting_order and self.purpose == "Send to Subcontractor": + # Get SCO Supplied Items Details + parent = frappe.qb.DocType("Subcontracting Order") + child = frappe.qb.DocType("Subcontracting Order Supplied Item") + + item_wh = ( + frappe.qb.from_(parent) + .inner_join(child) + .on(parent.name == child.parent) + .select(child.rm_item_code, child.reserve_warehouse) + .where(parent.name == self.subcontracting_order) + ).run(as_list=True) + + item_wh = frappe._dict(item_wh) for item in item_dict.values(): if self.pro_doc and cint(self.pro_doc.from_wip_warehouse): item["from_warehouse"] = self.pro_doc.wip_warehouse - # Get Reserve Warehouse from PO - if self.purchase_order and self.purpose == "Send to Subcontractor": + # Get Reserve Warehouse from SCO + if self.subcontracting_order and self.purpose == "Send to Subcontractor": item["from_warehouse"] = item_wh.get(item.item_code) item["to_warehouse"] = self.to_warehouse if self.purpose == "Send to Subcontractor" else "" @@ -1996,20 +2002,21 @@ class StockEntry(StockController): def update_subcontracting_order_supplied_items(self): if self.subcontracting_order and ( - self.purpose in ["Send to Subcontractor", "Material Transfer"] or self.is_return + self.purpose in ["Send to Subcontractor", "Material Transfer"] ): # Get SCO Supplied Items Details - item_wh = frappe._dict( - frappe.db.sql( - """ - select rm_item_code, reserve_warehouse - from `tabSubcontracting Order` sco, `tabSubcontracting Order Supplied Item` scoitemsup - where sco.name = scoitemsup.parent - and sco.name = %s""", - self.subcontracting_order, - ) - ) + parent = frappe.qb.DocType("Subcontracting Order") + child = frappe.qb.DocType("Subcontracting Order Supplied Item") + item_wh = ( + frappe.qb.from_(parent) + .inner_join(child) + .on(parent.name == child.parent) + .select(child.rm_item_code, child.reserve_warehouse) + .where(parent.name == self.subcontracting_order) + ).run(as_list=True) + + item_wh = frappe._dict(item_wh) supplied_items = get_supplied_items(self.subcontracting_order) for name, item in supplied_items.items(): @@ -2363,12 +2370,12 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None): return operating_cost_per_unit -def get_used_alternative_items(purchase_order=None, work_order=None): +def get_used_alternative_items(subcontracting_order=None, work_order=None): cond = "" - if purchase_order: - cond = "and ste.purpose = 'Send to Subcontractor' and ste.purchase_order = '{0}'".format( - purchase_order + if subcontracting_order: + cond = "and ste.purpose = 'Send to Subcontractor' and ste.subcontracting_order = '{0}'".format( + subcontracting_order ) elif work_order: cond = "and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{0}'".format( @@ -2422,7 +2429,6 @@ def get_valuation_rate_for_finished_good_entry(work_order): @frappe.whitelist() def get_uom_details(item_code, uom, qty): """Returns dict `{"conversion_factor": [value], "transfer_qty": qty * [value]}` - :param args: dict with `item_code`, `uom` and `qty`""" conversion_factor = get_conversion_factor(item_code, uom).get("conversion_factor") @@ -2542,3 +2548,36 @@ def get_supplied_items(subcontracting_order): ) return supplied_item_details + + +@frappe.whitelist() +def get_items_from_subcontracting_order(source_name, target_doc=None): + sco = frappe.get_doc("Subcontracting Order", source_name) + + if sco.docstatus == 1: + if target_doc and isinstance(target_doc, str): + target_doc = frappe.get_doc(json.loads(target_doc)) + + if target_doc.items: + target_doc.items = [] + + warehouses = {} + for item in sco.items: + warehouses[item.name] = item.warehouse + + for item in sco.supplied_items: + target_doc.append( + "items", + { + "s_warehouse": warehouses.get(item.reference_name), + "t_warehouse": sco.supplier_warehouse, + "item_code": item.rm_item_code, + "qty": item.required_qty, + "transfer_qty": item.required_qty, + "uom": item.stock_uom, + "stock_uom": item.stock_uom, + "conversion_factor": 1, + }, + ) + + return target_doc From f09fc46059731de85a16c752f78f146994769453 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 6 May 2022 17:32:03 +0530 Subject: [PATCH 21/98] refactor!: Purchase Receipt --- .../purchase_receipt/purchase_receipt.js | 3 - .../purchase_receipt/purchase_receipt.py | 13 - .../purchase_receipt/test_purchase_receipt.py | 276 ------------------ .../purchase_receipt/test_records.json | 32 -- .../purchase_receipt_item.json | 3 +- 5 files changed, 2 insertions(+), 325 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 51ec598f72..36948d0add 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -298,9 +298,6 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt frappe.provide("erpnext.buying"); frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) { - if (frm.doc.is_subcontracted) { - erpnext.buying.get_default_bom(frm); - } frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted); }); diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 1e1c0b9f7c..0e774426a2 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -231,7 +231,6 @@ class PurchaseReceipt(BuyingController): self.make_gl_entries() self.repost_future_sle_and_gle() - self.set_consumed_qty_in_po() def check_next_docstatus(self): submit_rv = frappe.db.sql( @@ -267,18 +266,6 @@ class PurchaseReceipt(BuyingController): self.repost_future_sle_and_gle() self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation") self.delete_auto_created_batches() - self.set_consumed_qty_in_po() - - @frappe.whitelist() - def get_current_stock(self): - for d in self.get("supplied_items"): - if self.supplier_warehouse: - bin = frappe.db.sql( - "select actual_qty from `tabBin` where item_code = %s and warehouse = %s", - (d.rm_item_code, self.supplier_warehouse), - as_dict=1, - ) - d.current_stock = bin and flt(bin[0]["actual_qty"]) or 0 def get_gl_entries(self, warehouse_account=None): from erpnext.accounts.general_ledger import process_gl_map diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index bfbdd56292..05021ce057 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2,10 +2,6 @@ # License: GNU General Public License v3. See license.txt -import json -import unittest -from collections import defaultdict - import frappe from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, cint, cstr, flt, today @@ -311,142 +307,6 @@ class TestPurchaseReceipt(FrappeTestCase): pr.cancel() self.assertTrue(get_gl_entries("Purchase Receipt", pr.name)) - def test_subcontracting(self): - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - - frappe.db.set_value( - "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM" - ) - - make_stock_entry( - item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100 - ) - make_stock_entry( - item_code="_Test Item Home Desktop 100", - qty=100, - target="_Test Warehouse 1 - _TC", - basic_rate=100, - ) - pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted=1) - self.assertEqual(len(pr.get("supplied_items")), 2) - - rm_supp_cost = sum(d.amount for d in pr.get("supplied_items")) - self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2)) - - pr.cancel() - - def test_subcontracting_gle_fg_item_rate_zero(self): - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - - frappe.db.set_value( - "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM" - ) - - se1 = make_stock_entry( - item_code="_Test Item", - target="Work In Progress - TCP1", - qty=100, - basic_rate=100, - company="_Test Company with perpetual inventory", - ) - - se2 = make_stock_entry( - item_code="_Test Item Home Desktop 100", - target="Work In Progress - TCP1", - qty=100, - basic_rate=100, - company="_Test Company with perpetual inventory", - ) - - pr = make_purchase_receipt( - item_code="_Test FG Item", - qty=10, - rate=0, - is_subcontracted=1, - company="_Test Company with perpetual inventory", - warehouse="Stores - TCP1", - supplier_warehouse="Work In Progress - TCP1", - ) - - gl_entries = get_gl_entries("Purchase Receipt", pr.name) - - self.assertFalse(gl_entries) - - pr.cancel() - se1.cancel() - se2.cancel() - - def test_subcontracting_over_receipt(self): - """ - Behaviour: Raise multiple PRs against one PO that in total - receive more than the required qty in the PO. - Expected Result: Error Raised for Over Receipt against PO. - """ - from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt - from erpnext.buying.doctype.purchase_order.purchase_order import ( - make_rm_stock_entry as make_subcontract_transfer_entry, - ) - from erpnext.buying.doctype.purchase_order.test_purchase_order import ( - create_purchase_order, - make_subcontracted_item, - update_backflush_based_on, - ) - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - - update_backflush_based_on("Material Transferred for Subcontract") - item_code = "_Test Subcontracted FG Item 1" - make_subcontracted_item(item_code=item_code) - - po = create_purchase_order( - item_code=item_code, - qty=1, - include_exploded_items=0, - is_subcontracted=1, - supplier_warehouse="_Test Warehouse 1 - _TC", - ) - - # stock raw materials in a warehouse before transfer - make_stock_entry( - target="_Test Warehouse - _TC", item_code="Test Extra Item 1", qty=10, basic_rate=100 - ) - make_stock_entry( - target="_Test Warehouse - _TC", item_code="_Test FG Item", qty=1, basic_rate=100 - ) - make_stock_entry( - target="_Test Warehouse - _TC", item_code="Test Extra Item 2", qty=1, basic_rate=100 - ) - - rm_items = [ - { - "item_code": item_code, - "rm_item_code": po.supplied_items[0].rm_item_code, - "item_name": "_Test FG Item", - "qty": po.supplied_items[0].required_qty, - "warehouse": "_Test Warehouse - _TC", - "stock_uom": "Nos", - }, - { - "item_code": item_code, - "rm_item_code": po.supplied_items[1].rm_item_code, - "item_name": "Test Extra Item 1", - "qty": po.supplied_items[1].required_qty, - "warehouse": "_Test Warehouse - _TC", - "stock_uom": "Nos", - }, - ] - rm_item_string = json.dumps(rm_items) - se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string)) - se.to_warehouse = "_Test Warehouse 1 - _TC" - se.save() - se.submit() - - pr1 = make_purchase_receipt(po.name) - pr2 = make_purchase_receipt(po.name) - - pr1.submit() - self.assertRaises(frappe.ValidationError, pr2.submit) - frappe.db.rollback() - def test_serial_no_supplier(self): pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) pr_row_1_serial_no = pr.get("items")[0].serial_no @@ -1095,103 +955,6 @@ class TestPurchaseReceipt(FrappeTestCase): pr.cancel() pr1.cancel() - def test_subcontracted_pr_for_multi_transfer_batches(self): - from erpnext.buying.doctype.purchase_order.purchase_order import ( - make_purchase_receipt, - make_rm_stock_entry, - ) - from erpnext.buying.doctype.purchase_order.test_purchase_order import ( - create_purchase_order, - update_backflush_based_on, - ) - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - - update_backflush_based_on("Material Transferred for Subcontract") - item_code = "_Test Subcontracted FG Item 3" - - make_item( - "Sub Contracted Raw Material 3", - {"is_stock_item": 1, "is_sub_contracted_item": 1, "has_batch_no": 1, "create_new_batch": 1}, - ) - - create_subcontracted_item( - item_code=item_code, has_batch_no=1, raw_materials=["Sub Contracted Raw Material 3"] - ) - - order_qty = 500 - po = create_purchase_order( - item_code=item_code, - qty=order_qty, - is_subcontracted=1, - supplier_warehouse="_Test Warehouse 1 - _TC", - ) - - ste1 = make_stock_entry( - target="_Test Warehouse - _TC", - item_code="Sub Contracted Raw Material 3", - qty=300, - basic_rate=100, - ) - ste2 = make_stock_entry( - target="_Test Warehouse - _TC", - item_code="Sub Contracted Raw Material 3", - qty=200, - basic_rate=100, - ) - - transferred_batch = {ste1.items[0].batch_no: 300, ste2.items[0].batch_no: 200} - - rm_items = [ - { - "item_code": item_code, - "rm_item_code": "Sub Contracted Raw Material 3", - "item_name": "_Test Item", - "qty": 300, - "warehouse": "_Test Warehouse - _TC", - "stock_uom": "Nos", - "name": po.supplied_items[0].name, - }, - { - "item_code": item_code, - "rm_item_code": "Sub Contracted Raw Material 3", - "item_name": "_Test Item", - "qty": 200, - "warehouse": "_Test Warehouse - _TC", - "stock_uom": "Nos", - "name": po.supplied_items[0].name, - }, - ] - - rm_item_string = json.dumps(rm_items) - se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string)) - self.assertEqual(len(se.items), 2) - se.items[0].batch_no = ste1.items[0].batch_no - se.items[1].batch_no = ste2.items[0].batch_no - se.submit() - - supplied_qty = frappe.db.get_value( - "Purchase Order Item Supplied", - {"parent": po.name, "rm_item_code": "Sub Contracted Raw Material 3"}, - "supplied_qty", - ) - - self.assertEqual(supplied_qty, 500.00) - - pr = make_purchase_receipt(po.name) - pr.save() - self.assertEqual(len(pr.supplied_items), 2) - - for row in pr.supplied_items: - self.assertEqual(transferred_batch.get(row.batch_no), row.consumed_qty) - - update_backflush_based_on("BOM") - - pr.delete() - se.cancel() - ste2.cancel() - ste1.cancel() - po.cancel() - def test_po_to_pi_and_po_to_pr_worflow_full(self): """Test following behaviour: - Create PO @@ -1520,44 +1283,5 @@ def make_purchase_receipt(**args): pr.submit() return pr - -def create_subcontracted_item(**args): - from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom - - args = frappe._dict(args) - - if not frappe.db.exists("Item", args.item_code): - make_item( - args.item_code, - { - "is_stock_item": 1, - "is_sub_contracted_item": 1, - "has_batch_no": args.get("has_batch_no") or 0, - }, - ) - - if not args.raw_materials: - if not frappe.db.exists("Item", "Test Extra Item 1"): - make_item( - "Test Extra Item 1", - { - "is_stock_item": 1, - }, - ) - - if not frappe.db.exists("Item", "Test Extra Item 2"): - make_item( - "Test Extra Item 2", - { - "is_stock_item": 1, - }, - ) - - args.raw_materials = ["_Test FG Item", "Test Extra Item 1"] - - if not frappe.db.get_value("BOM", {"item": args.item_code}, "name"): - make_bom(item=args.item_code, raw_materials=args.get("raw_materials")) - - test_dependencies = ["BOM", "Item Price", "Location"] test_records = frappe.get_test_records("Purchase Receipt") diff --git a/erpnext/stock/doctype/purchase_receipt/test_records.json b/erpnext/stock/doctype/purchase_receipt/test_records.json index 990ad12b30..e7ea9af6b9 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_records.json +++ b/erpnext/stock/doctype/purchase_receipt/test_records.json @@ -83,37 +83,5 @@ } ], "supplier": "_Test Supplier" - }, - - { - "buying_price_list": "_Test Price List", - "company": "_Test Company", - "conversion_rate": 1.0, - "currency": "INR", - "doctype": "Purchase Receipt", - "base_grand_total": 5000.0, - "is_subcontracted": 1, - "base_net_total": 5000.0, - "items": [ - { - "base_amount": 5000.0, - "conversion_factor": 1.0, - "description": "_Test FG Item", - "doctype": "Purchase Receipt Item", - "item_code": "_Test FG Item", - "item_name": "_Test FG Item", - "parentfield": "items", - "qty": 10.0, - "rate": 500.0, - "received_qty": 10.0, - "rejected_qty": 0.0, - "stock_uom": "_Test UOM", - "uom": "_Test UOM", - "warehouse": "_Test Warehouse - _TC", - "cost_center": "Main - _TC" - } - ], - "supplier": "_Test Supplier", - "supplier_warehouse": "_Test Warehouse - _TC" } ] \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 03a4201ce5..0e0605f00b 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -644,7 +644,8 @@ "label": "BOM", "no_copy": 1, "options": "BOM", - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "default": "0", From 5fa3f58c06af5679abaf143479e9ed00b325bbf9 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 9 May 2022 17:19:40 +0530 Subject: [PATCH 22/98] refactor!: Purchase Invoice --- .../purchase_invoice/purchase_invoice.js | 7 --- .../purchase_invoice/purchase_invoice.json | 2 +- .../purchase_invoice/purchase_invoice.py | 2 - .../purchase_invoice/test_purchase_invoice.py | 55 ------------------- .../purchase_invoice_item.json | 3 +- 5 files changed, 3 insertions(+), 66 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index ee29d2a744..696e534a8e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -570,13 +570,6 @@ frappe.ui.form.on("Purchase Invoice", { }); }, - is_subcontracted: function(frm) { - if (frm.doc.is_subcontracted) { - erpnext.buying.get_default_bom(frm); - } - frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted); - }, - update_stock: function(frm) { hide_fields(frm.doc); frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false); diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 9f87c5ab54..181dcc34de 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1365,7 +1365,7 @@ "width": "50px" }, { - "depends_on": "eval:doc.update_stock && doc.is_subcontracted", + "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", "label": "Supplier Warehouse", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index e6a46d0676..39a53d743c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -513,7 +513,6 @@ class PurchaseInvoice(BuyingController): # because updating ordered qty in bin depends upon updated ordered qty in PO if self.update_stock == 1: self.update_stock_ledger() - self.set_consumed_qty_in_po() from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit update_serial_nos_after_submit(self, "items") @@ -1403,7 +1402,6 @@ class PurchaseInvoice(BuyingController): if self.update_stock == 1: self.update_stock_ledger() self.delete_auto_created_batches() - self.set_consumed_qty_in_po() self.make_gl_entries_on_cancel() diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 73390dd6f4..67d4695867 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -468,37 +468,6 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(tax.tax_amount, expected_values[i][1]) self.assertEqual(tax.total, expected_values[i][2]) - def test_purchase_invoice_with_subcontracted_item(self): - wrapper = frappe.copy_doc(test_records[0]) - wrapper.get("items")[0].item_code = "_Test FG Item" - wrapper.insert() - wrapper.load_from_db() - - expected_values = [["_Test FG Item", 90, 59], ["_Test Item Home Desktop 200", 135, 177]] - for i, item in enumerate(wrapper.get("items")): - self.assertEqual(item.item_code, expected_values[i][0]) - self.assertEqual(item.item_tax_amount, expected_values[i][1]) - self.assertEqual(item.valuation_rate, expected_values[i][2]) - - self.assertEqual(wrapper.base_net_total, 1250) - - # tax amounts - expected_values = [ - ["_Test Account Shipping Charges - _TC", 100, 1350], - ["_Test Account Customs Duty - _TC", 125, 1350], - ["_Test Account Excise Duty - _TC", 140, 1490], - ["_Test Account Education Cess - _TC", 2.8, 1492.8], - ["_Test Account S&H Education Cess - _TC", 1.4, 1494.2], - ["_Test Account CST - _TC", 29.88, 1524.08], - ["_Test Account VAT - _TC", 156.25, 1680.33], - ["_Test Account Discount - _TC", 168.03, 1512.30], - ] - - for i, tax in enumerate(wrapper.get("taxes")): - self.assertEqual(tax.account_head, expected_values[i][0]) - self.assertEqual(tax.tax_amount, expected_values[i][1]) - self.assertEqual(tax.total, expected_values[i][2]) - def test_purchase_invoice_with_advance(self): from erpnext.accounts.doctype.journal_entry.test_journal_entry import ( test_records as jv_test_records, @@ -885,30 +854,6 @@ class TestPurchaseInvoice(unittest.TestCase): pi.cancel() self.assertEqual(actual_qty_0, get_qty_after_transaction()) - def test_subcontracting_via_purchase_invoice(self): - from erpnext.buying.doctype.purchase_order.test_purchase_order import update_backflush_based_on - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - - update_backflush_based_on("BOM") - make_stock_entry( - item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100 - ) - make_stock_entry( - item_code="_Test Item Home Desktop 100", - target="_Test Warehouse 1 - _TC", - qty=100, - basic_rate=100, - ) - - pi = make_purchase_invoice( - item_code="_Test FG Item", qty=10, rate=500, update_stock=1, is_subcontracted=1 - ) - - self.assertEqual(len(pi.get("supplied_items")), 2) - - rm_supp_cost = sum(d.amount for d in pi.get("supplied_items")) - self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2)) - def test_rejected_serial_no(self): pi = make_purchase_invoice( item_code="_Test Serialized Item With Series", diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 6651195e5f..dd62886d96 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -619,7 +619,8 @@ "fieldname": "bom", "fieldtype": "Link", "label": "BOM", - "options": "BOM" + "options": "BOM", + "read_only": 1 }, { "default": "0", From 3469560105d0e3fee72a90d7f24b10476e380dba Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 11 May 2022 09:17:07 +0530 Subject: [PATCH 23/98] refactor!: Accounts Controller --- erpnext/controllers/accounts_controller.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 8a9318e184..1790a0e2ec 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2586,10 +2586,6 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.update_ordered_qty() parent.update_ordered_and_reserved_qty() parent.update_receiving_percentage() - if parent.is_subcontracted: - parent.update_reserved_qty_for_subcontract() - parent.create_raw_materials_supplied("supplied_items") - parent.save() else: parent.update_reserved_qty() parent.update_project() From 8486bbf31a96ead1653f23ac4d9f3e7dd78078ef Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 13 May 2022 14:40:14 +0530 Subject: [PATCH 24/98] refactor!: Subcontract Order Summary --- .../subcontract_order_summary.js | 14 ++--- .../subcontract_order_summary.json | 2 +- .../subcontract_order_summary.py | 57 +++++++++---------- 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js index 6889322fb9..976ff60440 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js @@ -14,32 +14,32 @@ frappe.query_reports["Subcontract Order Summary"] = { }, { label: __("From Date"), - fieldname:"from_date", + fieldname: "from_date", fieldtype: "Date", default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), reqd: 1 }, { label: __("To Date"), - fieldname:"to_date", + fieldname: "to_date", fieldtype: "Date", default: frappe.datetime.get_today(), reqd: 1 }, { - label: __("Purchase Order"), + label: __("Subcontracting Order"), fieldname: "name", fieldtype: "Link", - options: "Purchase Order", - get_query: function() { + options: "Subcontracting Order", + get_query: function () { return { filters: { docstatus: 1, - is_subcontracted: 1, + company: frappe.query_report.get_filter_value('company') } } } } ] -}; +}; \ No newline at end of file diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json index 526a8d8ad0..7861e49ccf 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json @@ -15,7 +15,7 @@ "name": "Subcontract Order Summary", "owner": "Administrator", "prepared_report": 0, - "ref_doctype": "Purchase Order", + "ref_doctype": "Subcontracting Order", "report_name": "Subcontract Order Summary", "report_type": "Script Report", "roles": [ diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py index 3d66637576..3750daa71e 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py @@ -20,34 +20,33 @@ def get_data(report_filters): if orders: supplied_items = get_supplied_items(orders, report_filters) - po_details = prepare_subcontracted_data(orders, supplied_items) - get_subcontracted_data(po_details, data) + sco_details = prepare_subcontracted_data(orders, supplied_items) + get_subcontracted_data(sco_details, data) return data def get_subcontracted_orders(report_filters): fields = [ - "`tabPurchase Order Item`.`parent` as po_id", - "`tabPurchase Order Item`.`item_code`", - "`tabPurchase Order Item`.`item_name`", - "`tabPurchase Order Item`.`qty`", - "`tabPurchase Order Item`.`name`", - "`tabPurchase Order Item`.`received_qty`", - "`tabPurchase Order`.`status`", + "`tabSubcontracting Order Item`.`parent` as sco_id", + "`tabSubcontracting Order Item`.`item_code`", + "`tabSubcontracting Order Item`.`item_name`", + "`tabSubcontracting Order Item`.`qty`", + "`tabSubcontracting Order Item`.`name`", + "`tabSubcontracting Order Item`.`received_qty`", + "`tabSubcontracting Order`.`status`", ] filters = get_filters(report_filters) - return frappe.get_all("Purchase Order", fields=fields, filters=filters) or [] + return frappe.get_all("Subcontracting Order", fields=fields, filters=filters) or [] def get_filters(report_filters): filters = [ - ["Purchase Order", "docstatus", "=", 1], - ["Purchase Order", "is_subcontracted", "=", 1], + ["Subcontracting Order", "docstatus", "=", 1], [ - "Purchase Order", + "Subcontracting Order", "transaction_date", "between", (report_filters.from_date, report_filters.to_date), @@ -56,7 +55,7 @@ def get_filters(report_filters): for field in ["name", "company"]: if report_filters.get(field): - filters.append(["Purchase Order", field, "=", report_filters.get(field)]) + filters.append(["Subcontracting Order", field, "=", report_filters.get(field)]) return filters @@ -71,16 +70,15 @@ def get_supplied_items(orders, report_filters): "rm_item_code", "required_qty", "supplied_qty", - "returned_qty", "total_supplied_qty", "consumed_qty", "reference_name", ] - filters = {"parent": ("in", [d.po_id for d in orders]), "docstatus": 1} + filters = {"parent": ("in", [d.sco_id for d in orders]), "docstatus": 1} supplied_items = {} - for row in frappe.get_all("Purchase Order Item Supplied", fields=fields, filters=filters): + for row in frappe.get_all("Subcontracting Order Supplied Item", fields=fields, filters=filters): new_key = (row.parent, row.reference_name, row.main_item_code) supplied_items.setdefault(new_key, []).append(row) @@ -89,24 +87,24 @@ def get_supplied_items(orders, report_filters): def prepare_subcontracted_data(orders, supplied_items): - po_details = {} + sco_details = {} for row in orders: - key = (row.po_id, row.name, row.item_code) - if key not in po_details: - po_details.setdefault(key, frappe._dict({"po_item": row, "supplied_items": []})) + key = (row.sco_id, row.name, row.item_code) + if key not in sco_details: + sco_details.setdefault(key, frappe._dict({"sco_item": row, "supplied_items": []})) - details = po_details[key] + details = sco_details[key] if supplied_items.get(key): for supplied_item in supplied_items[key]: details["supplied_items"].append(supplied_item) - return po_details + return sco_details -def get_subcontracted_data(po_details, data): - for key, details in po_details.items(): - res = details.po_item +def get_subcontracted_data(sco_details, data): + for key, details in sco_details.items(): + res = details.sco_item for index, row in enumerate(details.supplied_items): if index != 0: res = {} @@ -118,10 +116,10 @@ def get_subcontracted_data(po_details, data): def get_columns(): return [ { - "label": _("Purchase Order"), - "fieldname": "po_id", + "label": _("Subcontracting Order"), + "fieldname": "sco_id", "fieldtype": "Link", - "options": "Purchase Order", + "options": "Subcontracting Order", "width": 100, }, {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 80}, @@ -144,5 +142,4 @@ def get_columns(): {"label": _("Required Qty"), "fieldname": "required_qty", "fieldtype": "Float", "width": 110}, {"label": _("Supplied Qty"), "fieldname": "supplied_qty", "fieldtype": "Float", "width": 110}, {"label": _("Consumed Qty"), "fieldname": "consumed_qty", "fieldtype": "Float", "width": 120}, - {"label": _("Returned Qty"), "fieldname": "returned_qty", "fieldtype": "Float", "width": 110}, ] From 3be663b121824e929320e57cba6462cec83e7092 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 13 May 2022 14:43:07 +0530 Subject: [PATCH 25/98] refactor!: Subcontracted Item To Be Received --- .../subcontracted_item_to_be_received.py | 27 +++++----- .../test_subcontracted_item_to_be_received.py | 50 ++++++++++++------- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py index 2e90de66ef..30f9dec4d0 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py +++ b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py @@ -19,10 +19,10 @@ def execute(filters=None): def get_columns(): return [ { - "label": _("Purchase Order"), + "label": _("Subcontracting Order"), "fieldtype": "Link", - "fieldname": "purchase_order", - "options": "Purchase Order", + "fieldname": "subcontracting_order", + "options": "Subcontracting Order", "width": 150, }, {"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "hidden": 1, "width": 150}, @@ -57,14 +57,14 @@ def get_columns(): def get_data(data, filters): - po = get_po(filters) - po_name = [v.name for v in po] - sub_items = get_purchase_order_item_supplied(po_name) + sco = get_sco(filters) + sco_name = [v.name for v in sco] + sub_items = get_subcontracting_order_item_supplied(sco_name) for item in sub_items: - for order in po: + for order in sco: if order.name == item.parent and item.received_qty < item.qty: row = { - "purchase_order": item.parent, + "subcontracting_order": item.parent, "date": order.transaction_date, "supplier": order.supplier, "fg_item_code": item.item_code, @@ -76,22 +76,21 @@ def get_data(data, filters): data.append(row) -def get_po(filters): +def get_sco(filters): record_filters = [ - ["is_subcontracted", "=", 1], ["supplier", "=", filters.supplier], ["transaction_date", "<=", filters.to_date], ["transaction_date", ">=", filters.from_date], ["docstatus", "=", 1], ] return frappe.get_all( - "Purchase Order", filters=record_filters, fields=["name", "transaction_date", "supplier"] + "Subcontracting Order", filters=record_filters, fields=["name", "transaction_date", "supplier"] ) -def get_purchase_order_item_supplied(po): +def get_subcontracting_order_item_supplied(sco): return frappe.get_all( - "Purchase Order Item", - filters=[("parent", "IN", po)], + "Subcontracting Order Item", + filters=[("parent", "IN", sco)], fields=["parent", "item_code", "item_name", "qty", "received_qty"], ) diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py index 57f8741b5b..80fd657f41 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py +++ b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py @@ -7,18 +7,35 @@ import frappe from frappe.tests.utils import FrappeTestCase -from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt -from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.buying.report.subcontracted_item_to_be_received.subcontracted_item_to_be_received import ( execute, ) +from erpnext.controllers.tests.test_subcontracting_controller import ( + get_subcontracting_order, + make_service_item, +) from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry +from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + make_subcontracting_receipt, +) class TestSubcontractedItemToBeReceived(FrappeTestCase): def test_pending_and_received_qty(self): - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) - transfer_param = [] + make_service_item("Subcontracted Service Item 1") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 10, + "rate": 500, + "fg_item": "_Test FG Item", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order( + service_items=service_items, supplier_warehouse="_Test Warehouse 1 - _TC" + ) make_stock_entry( item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100 ) @@ -28,28 +45,27 @@ class TestSubcontractedItemToBeReceived(FrappeTestCase): qty=100, basic_rate=100, ) - make_purchase_receipt_against_po(po.name) - po.reload() + make_subcontracting_receipt_against_sco(sco.name) + sco.reload() col, data = execute( filters=frappe._dict( { - "supplier": po.supplier, + "supplier": sco.supplier, "from_date": frappe.utils.get_datetime( - frappe.utils.add_to_date(po.transaction_date, days=-10) + frappe.utils.add_to_date(sco.transaction_date, days=-10) ), - "to_date": frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10)), + "to_date": frappe.utils.get_datetime(frappe.utils.add_to_date(sco.transaction_date, days=10)), } ) ) self.assertEqual(data[0]["pending_qty"], 5) self.assertEqual(data[0]["received_qty"], 5) - self.assertEqual(data[0]["purchase_order"], po.name) - self.assertEqual(data[0]["supplier"], po.supplier) + self.assertEqual(data[0]["subcontracting_order"], sco.name) + self.assertEqual(data[0]["supplier"], sco.supplier) -def make_purchase_receipt_against_po(po, quantity=5): - pr = make_purchase_receipt(po) - pr.items[0].qty = quantity - pr.supplier_warehouse = "_Test Warehouse 1 - _TC" - pr.insert() - pr.submit() +def make_subcontracting_receipt_against_sco(sco, quantity=5): + scr = make_subcontracting_receipt(sco) + scr.items[0].qty = quantity + scr.insert() + scr.submit() From 05f05ab75b69bac8527e8651825a9e5f836b6c6d Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 13 May 2022 14:46:44 +0530 Subject: [PATCH 26/98] refactor!: Subcontracted Item To Be Transferred --- ...tracted_raw_materials_to_be_transferred.py | 27 ++++--- ...tracted_raw_materials_to_be_transferred.py | 76 ++++++++++--------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py index 6b8a3b140a..a837b24357 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py @@ -46,10 +46,10 @@ def get_columns(): def get_data(filters): - po_rm_item_details = get_po_items_to_supply(filters) + sco_rm_item_details = get_sco_items_to_supply(filters) data = [] - for row in po_rm_item_details: + for row in sco_rm_item_details: transferred_qty = row.get("transferred_qty") or 0 if transferred_qty < row.get("reqd_qty", 0): pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty) @@ -59,23 +59,22 @@ def get_data(filters): return data -def get_po_items_to_supply(filters): +def get_sco_items_to_supply(filters): return frappe.db.get_all( - "Purchase Order", + "Subcontracting Order", fields=[ - "name as purchase_order", + "name as subcontracting_order", "transaction_date as date", "supplier as supplier", - "`tabPurchase Order Item Supplied`.rm_item_code as rm_item_code", - "`tabPurchase Order Item Supplied`.required_qty as reqd_qty", - "`tabPurchase Order Item Supplied`.supplied_qty as transferred_qty", + "`tabSubcontracting Order Supplied Item`.rm_item_code as rm_item_code", + "`tabSubcontracting Order Supplied Item`.required_qty as reqd_qty", + "`tabSubcontracting Order Supplied Item`.supplied_qty as transferred_qty", ], filters=[ - ["Purchase Order", "per_received", "<", "100"], - ["Purchase Order", "is_subcontracted", "=", 1], - ["Purchase Order", "supplier", "=", filters.supplier], - ["Purchase Order", "transaction_date", "<=", filters.to_date], - ["Purchase Order", "transaction_date", ">=", filters.from_date], - ["Purchase Order", "docstatus", "=", 1], + ["Subcontracting Order", "per_received", "<", "100"], + ["Subcontracting Order", "supplier", "=", filters.supplier], + ["Subcontracting Order", "transaction_date", "<=", filters.to_date], + ["Subcontracting Order", "transaction_date", ">=", filters.from_date], + ["Subcontracting Order", "docstatus", "=", 1], ], ) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py index 2791a26db7..d29791cebf 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py @@ -3,24 +3,36 @@ # Compiled at: 2019-05-06 10:24:35 # Decompiled by https://python-decompiler.com -import json - import frappe from frappe.tests.utils import FrappeTestCase -from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry -from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import ( execute, ) +from erpnext.controllers.tests.test_subcontracting_controller import ( + get_subcontracting_order, + make_service_item, +) from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry +from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + make_rm_stock_entry, +) class TestSubcontractedItemToBeTransferred(FrappeTestCase): def test_pending_and_transferred_qty(self): - po = create_purchase_order( - item_code="_Test FG Item", is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" - ) + make_service_item("Subcontracted Service Item 1") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 10, + "rate": 500, + "fg_item": "_Test FG Item", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) # Material Receipt of RMs make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=100, basic_rate=100) @@ -28,50 +40,47 @@ class TestSubcontractedItemToBeTransferred(FrappeTestCase): item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", qty=100, basic_rate=100 ) - se = transfer_subcontracted_raw_materials(po) + transfer_subcontracted_raw_materials(sco) col, data = execute( filters=frappe._dict( { - "supplier": po.supplier, + "supplier": sco.supplier, "from_date": frappe.utils.get_datetime( - frappe.utils.add_to_date(po.transaction_date, days=-10) + frappe.utils.add_to_date(sco.transaction_date, days=-10) ), - "to_date": frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10)), + "to_date": frappe.utils.get_datetime(frappe.utils.add_to_date(sco.transaction_date, days=10)), } ) ) - po.reload() + sco.reload() - po_data = [row for row in data if row.get("purchase_order") == po.name] + sco_data = [row for row in data if row.get("subcontracting_order") == sco.name] # Alphabetically sort to be certain of order - po_data = sorted(po_data, key=lambda i: i["rm_item_code"]) + sco_data = sorted(sco_data, key=lambda i: i["rm_item_code"]) - self.assertEqual(len(po_data), 2) - self.assertEqual(po_data[0]["purchase_order"], po.name) + self.assertEqual(len(sco_data), 2) + self.assertEqual(sco_data[0]["subcontracting_order"], sco.name) - self.assertEqual(po_data[0]["rm_item_code"], "_Test Item") - self.assertEqual(po_data[0]["p_qty"], 8) - self.assertEqual(po_data[0]["transferred_qty"], 2) + self.assertEqual(sco_data[0]["rm_item_code"], "_Test Item") + self.assertEqual(sco_data[0]["p_qty"], 8) + self.assertEqual(sco_data[0]["transferred_qty"], 2) - self.assertEqual(po_data[1]["rm_item_code"], "_Test Item Home Desktop 100") - self.assertEqual(po_data[1]["p_qty"], 19) - self.assertEqual(po_data[1]["transferred_qty"], 1) - - se.cancel() - po.cancel() + self.assertEqual(sco_data[1]["rm_item_code"], "_Test Item Home Desktop 100") + self.assertEqual(sco_data[1]["p_qty"], 19) + self.assertEqual(sco_data[1]["transferred_qty"], 1) -def transfer_subcontracted_raw_materials(po): - # Order of supplied items fetched in PO is flaky +def transfer_subcontracted_raw_materials(sco): + # Order of supplied items fetched in SCO is flaky transfer_qty_map = {"_Test Item": 2, "_Test Item Home Desktop 100": 1} - item_1 = po.supplied_items[0].rm_item_code - item_2 = po.supplied_items[1].rm_item_code + item_1 = sco.supplied_items[0].rm_item_code + item_2 = sco.supplied_items[1].rm_item_code - rm_item = [ + rm_items = [ { - "name": po.supplied_items[0].name, + "name": sco.supplied_items[0].name, "item_code": item_1, "rm_item_code": item_1, "item_name": item_1, @@ -82,7 +91,7 @@ def transfer_subcontracted_raw_materials(po): "stock_uom": "Nos", }, { - "name": po.supplied_items[1].name, + "name": sco.supplied_items[1].name, "item_code": item_2, "rm_item_code": item_2, "item_name": item_2, @@ -93,8 +102,7 @@ def transfer_subcontracted_raw_materials(po): "stock_uom": "Nos", }, ] - rm_item_string = json.dumps(rm_item) - se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string)) + se = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items)) se.from_warehouse = "_Test Warehouse - _TC" se.to_warehouse = "_Test Warehouse - _TC" se.stock_entry_type = "Send to Subcontractor" From 92625902ad9f8b28d4d1cb203c4647984eff8ac5 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 13 May 2022 14:48:34 +0530 Subject: [PATCH 27/98] refactor!: BOM --- erpnext/manufacturing/doctype/bom/test_bom.py | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 62fc0724e0..e6d4e4446b 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -9,14 +9,13 @@ import frappe from frappe.tests.utils import FrappeTestCase from frappe.utils import cstr, flt -from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order +from erpnext.controllers.tests.test_subcontracting_controller import set_backflush_based_on from erpnext.manufacturing.doctype.bom.bom import item_query, make_variant_bom from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( create_stock_reconciliation, ) -from erpnext.tests.test_subcontracting import set_backflush_based_on test_records = frappe.get_test_records("BOM") test_dependencies = ["Item", "Quality Inspection Template"] @@ -249,12 +248,29 @@ class TestBOM(FrappeTestCase): bom.submit() # test that sourced_by_supplier rate is zero even after updating cost self.assertEqual(bom.items[2].rate, 0) - # test in Purchase Order sourced_by_supplier is not added to Supplied Item - po = create_purchase_order( - item_code=item_code, qty=1, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" + + from erpnext.controllers.tests.test_subcontracting_controller import ( + get_subcontracting_order, + make_service_item, + ) + + make_service_item("Subcontracted Service Item 1") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 1, + "rate": 100, + "fg_item": item_code, + "fg_item_qty": 1, + }, + ] + # test in Subcontracting Order sourced_by_supplier is not added to Supplied Item + sco = get_subcontracting_order( + service_items=service_items, supplier_warehouse="_Test Warehouse 1 - _TC" ) bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1]) - supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) + supplied_items = sorted([d.rm_item_code for d in sco.supplied_items]) self.assertEqual(bom_items, supplied_items) def test_bom_tree_representation(self): From 6c794afbe7d1aa253ed57dad78d1d4a99258d247 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 13 May 2022 14:50:12 +0530 Subject: [PATCH 28/98] refactor!: Item Alternative --- .../item_alternative/test_item_alternative.py | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py index 32c58c5ae1..3f66a6a695 100644 --- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py @@ -1,17 +1,15 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import json - import frappe from frappe.tests.utils import FrappeTestCase from frappe.utils import flt -from erpnext.buying.doctype.purchase_order.purchase_order import ( - make_purchase_receipt, - make_rm_stock_entry, +from erpnext.controllers.tests.test_subcontracting_controller import ( + get_subcontracting_order, + make_service_item, + set_backflush_based_on, ) -from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry @@ -19,6 +17,10 @@ from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( create_stock_reconciliation, ) +from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + make_rm_stock_entry, + make_subcontracting_receipt, +) class TestItemAlternative(FrappeTestCase): @@ -27,9 +29,7 @@ class TestItemAlternative(FrappeTestCase): make_items() def test_alternative_item_for_subcontract_rm(self): - frappe.db.set_value( - "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM" - ) + set_backflush_based_on("BOM") create_stock_reconciliation( item_code="Alternate Item For A RW 1", warehouse="_Test Warehouse - _TC", qty=5, rate=2000 @@ -39,15 +39,22 @@ class TestItemAlternative(FrappeTestCase): ) supplier_warehouse = "Test Supplier Warehouse - _TC" - po = create_purchase_order( - item="Test Finished Goods - A", - is_subcontracted=1, - qty=5, - rate=3000, - supplier_warehouse=supplier_warehouse, - ) - rm_item = [ + make_service_item("Subcontracted Service Item 1") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 5, + "rate": 3000, + "fg_item": "Test Finished Goods - A", + "fg_item_qty": 5, + }, + ] + sco = get_subcontracting_order( + service_items=service_items, supplier_warehouse=supplier_warehouse + ) + rm_items = [ { "item_code": "Test Finished Goods - A", "rm_item_code": "Test FG A RW 1", @@ -70,14 +77,13 @@ class TestItemAlternative(FrappeTestCase): }, ] - rm_item_string = json.dumps(rm_item) reserved_qty_for_sub_contract = frappe.db.get_value( "Bin", {"item_code": "Test FG A RW 1", "warehouse": "_Test Warehouse - _TC"}, "reserved_qty_for_sub_contract", ) - se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string)) + se = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items)) se.to_warehouse = supplier_warehouse se.insert() @@ -101,22 +107,17 @@ class TestItemAlternative(FrappeTestCase): after_transfer_reserved_qty_for_sub_contract, flt(reserved_qty_for_sub_contract - 5) ) - pr = make_purchase_receipt(po.name) - pr.save() + scr = make_subcontracting_receipt(sco.name) + scr.save() - pr = frappe.get_doc("Purchase Receipt", pr.name) + scr = frappe.get_doc("Subcontracting Receipt", scr.name) status = False - for d in pr.supplied_items: - if d.rm_item_code == "Alternate Item For A RW 1": + for item in scr.supplied_items: + if item.rm_item_code == "Alternate Item For A RW 1": status = True self.assertEqual(status, True) - frappe.db.set_value( - "Buying Settings", - None, - "backflush_raw_materials_of_subcontract_based_on", - "Material Transferred for Subcontract", - ) + set_backflush_based_on("Material Transferred for Subcontract") def test_alternative_item_for_production_rm(self): create_stock_reconciliation( From fcc09592b98ebb3e39cf0168d39f39172d5e0728 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 13 May 2022 14:51:49 +0530 Subject: [PATCH 29/98] refactor!: Stock Ledger Entry --- .../test_stock_ledger_entry.py | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 6561362c3a..7920b40c12 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -405,61 +405,6 @@ class TestStockLedgerEntry(FrappeTestCase): lcv.cancel() pr.cancel() - def test_sub_contracted_item_costing(self): - from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom - - company = "_Test Company" - rm_item_code = "_Test Item for Reposting" - subcontracted_item = "_Test Subcontracted Item for Reposting" - - frappe.db.set_value( - "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM" - ) - make_bom(item=subcontracted_item, raw_materials=[rm_item_code], currency="INR") - - # Purchase raw materials on supplier warehouse: Qty = 50, Rate = 100 - pr = make_purchase_receipt( - company=company, - posting_date="2020-04-10", - warehouse="Stores - _TC", - item_code=rm_item_code, - qty=10, - rate=100, - ) - - # Purchase Receipt for subcontracted item - pr1 = make_purchase_receipt( - company=company, - posting_date="2020-04-20", - warehouse="Finished Goods - _TC", - supplier_warehouse="Stores - _TC", - item_code=subcontracted_item, - qty=10, - rate=20, - is_subcontracted=1, - ) - - self.assertEqual(pr1.items[0].valuation_rate, 120) - - # Update raw material's valuation via LCV, Additional cost = 50 - lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) - - pr1.reload() - self.assertEqual(pr1.items[0].valuation_rate, 125) - - # check outgoing_rate for DN after reposting - incoming_rate = frappe.db.get_value( - "Stock Ledger Entry", - {"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "item_code": subcontracted_item}, - "incoming_rate", - ) - self.assertEqual(incoming_rate, 125) - - # cleanup data - pr1.cancel() - lcv.cancel() - pr.cancel() - def test_back_dated_entry_not_allowed(self): # Back dated stock transactions are only allowed to stock managers frappe.db.set_value( From 323bdf85ce4398759d9bda456b5de18d3ee31d73 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 17 May 2022 15:14:07 +0530 Subject: [PATCH 30/98] feat: SL and GL reposting --- .../item_reposting_for_incorrect_sl_and_gl.py | 2 ++ erpnext/stock/stock_ledger.py | 20 +++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index f6427ca55a..75a5477be8 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -15,6 +15,8 @@ def execute(): ("accounts", "sales_invoice_item"), ("accounts", "purchase_invoice_item"), ("buying", "purchase_receipt_item_supplied"), + ("subcontracting", "subcontracting_receipt_item"), + ("subcontracting", "subcontracting_receipt_supplied_item"), ] for module, doctype in doctypes_to_reload: diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 3e0ddab6d3..f716ff6d9b 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -619,6 +619,7 @@ class update_entries_after(object): "Purchase Invoice", "Delivery Note", "Sales Invoice", + "Subcontracting Receipt", ): if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_return"): from erpnext.controllers.sales_and_purchase_return import ( @@ -635,6 +636,8 @@ class update_entries_after(object): else: if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"): rate_field = "valuation_rate" + elif sle.voucher_type == "Subcontracting Receipt": + rate_field = "rate" else: rate_field = "incoming_rate" @@ -648,6 +651,8 @@ class update_entries_after(object): else: if sle.voucher_type in ("Delivery Note", "Sales Invoice"): ref_doctype = "Packed Item" + elif sle == "Subcontracting Receipt": + ref_doctype = "Subcontracting Receipt Supplied Item" else: ref_doctype = "Purchase Receipt Item Supplied" @@ -673,6 +678,8 @@ class update_entries_after(object): self.update_rate_on_delivery_and_sales_return(sle, outgoing_rate) elif flt(sle.actual_qty) < 0 and sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"): self.update_rate_on_purchase_receipt(sle, outgoing_rate) + elif flt(sle.actual_qty) < 0 and sle.voucher_type == "Subcontracting Receipt": + self.update_rate_on_subcontracting_receipt(sle, outgoing_rate) def update_rate_on_stock_entry(self, sle, outgoing_rate): frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate) @@ -714,12 +721,13 @@ class update_entries_after(object): "Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate ) - # Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice - if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"): - doc = frappe.get_doc(sle.voucher_type, sle.voucher_no) - doc.update_valuation_rate(reset_outgoing_rate=False) - for d in doc.items + doc.supplied_items: - d.db_update() + def update_rate_on_subcontracting_receipt(self, sle, outgoing_rate): + if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no): + frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "rate", outgoing_rate) + else: + frappe.db.set_value( + "Subcontracting Receipt Supplied Item", sle.voucher_detail_no, "rate", outgoing_rate + ) def get_serialized_values(self, sle): incoming_rate = flt(sle.incoming_rate) From e9b28452e4997055e505b6c4877cf4664ccb262f Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 18 May 2022 22:42:25 +0530 Subject: [PATCH 31/98] feat: SCR return --- .../controllers/sales_and_purchase_return.py | 48 +++++++++------ .../subcontracting_receipt.js | 2 +- .../test_subcontracting_receipt.py | 59 +++++++++++++++++++ 3 files changed, 91 insertions(+), 18 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 9642c24a9e..ca968e939e 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -77,7 +77,7 @@ def validate_returned_items(doc): if doc.doctype != "Purchase Invoice": select_fields += ",serial_no, batch_no" - if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]: + if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]: select_fields += ",rejected_qty, received_qty" for d in frappe.db.sql( @@ -161,7 +161,7 @@ def validate_returned_items(doc): def validate_quantity(doc, args, ref, valid_items, already_returned_items): fields = ["stock_qty"] - if doc.doctype in ["Purchase Receipt", "Purchase Invoice"]: + if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]: fields.extend(["received_qty", "rejected_qty"]) already_returned_data = already_returned_items.get(args.item_code) or {} @@ -224,7 +224,7 @@ def get_ref_item_dict(valid_items, ref_item_row): if ref_item_row.get("rate", 0) > item_dict["rate"]: item_dict["rate"] = ref_item_row.get("rate", 0) - if ref_item_row.parenttype in ["Purchase Invoice", "Purchase Receipt"]: + if ref_item_row.parenttype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]: item_dict["received_qty"] += ref_item_row.received_qty item_dict["rejected_qty"] += ref_item_row.rejected_qty @@ -239,7 +239,7 @@ def get_ref_item_dict(valid_items, ref_item_row): def get_already_returned_items(doc): column = "child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty" - if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]: + if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]: column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty, sum(abs(child.received_qty) * child.conversion_factor) as received_qty""" @@ -281,17 +281,21 @@ def get_returned_qty_map_for_row(return_against, party, row_name, doctype): child_doctype = doctype + " Item" reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype) - if doctype in ("Purchase Receipt", "Purchase Invoice"): + if doctype in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"): party_type = "supplier" else: party_type = "customer" fields = [ "sum(abs(`tab{0}`.qty)) as qty".format(child_doctype), - "sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype), ] - if doctype in ("Purchase Receipt", "Purchase Invoice"): + if doctype != "Subcontracting Receipt": + fields += [ + "sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype), + ] + + if doctype in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"): fields += [ "sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype), "sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype), @@ -397,7 +401,7 @@ def make_return_doc(doctype, source_name, target_doc=None): if serial_nos: target_doc.serial_no = "\n".join(serial_nos) - if doctype == "Purchase Receipt": + if doctype in ["Purchase Receipt", "Subcontracting Receipt"]: returned_qty_map = get_returned_qty_map_for_row( source_parent.name, source_parent.supplier, source_doc.name, doctype ) @@ -409,15 +413,24 @@ def make_return_doc(doctype, source_name, target_doc=None): ) target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0)) - target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0)) - target_doc.received_stock_qty = -1 * flt( - source_doc.received_stock_qty - (returned_qty_map.get("received_stock_qty") or 0) - ) + if hasattr(target_doc, "stock_qty"): + target_doc.stock_qty = -1 * flt( + source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0) + ) + target_doc.received_stock_qty = -1 * flt( + source_doc.received_stock_qty - (returned_qty_map.get("received_stock_qty") or 0) + ) - target_doc.purchase_order = source_doc.purchase_order - target_doc.purchase_order_item = source_doc.purchase_order_item - target_doc.rejected_warehouse = source_doc.rejected_warehouse - target_doc.purchase_receipt_item = source_doc.name + if doctype == "Subcontracting Receipt": + target_doc.subcontracting_order = source_doc.subcontracting_order + target_doc.subcontracting_order_item = source_doc.subcontracting_order_item + target_doc.rejected_warehouse = source_doc.rejected_warehouse + target_doc.subcontracting_receipt_item = source_doc.name + else: + target_doc.purchase_order = source_doc.purchase_order + target_doc.purchase_order_item = source_doc.purchase_order_item + target_doc.rejected_warehouse = source_doc.rejected_warehouse + target_doc.purchase_receipt_item = source_doc.name elif doctype == "Purchase Invoice": returned_qty_map = get_returned_qty_map_for_row( @@ -529,7 +542,7 @@ def get_rate_for_return( item_row, ) - if voucher_type in ("Purchase Receipt", "Purchase Invoice"): + if voucher_type in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"): select_field = "incoming_rate" else: select_field = "abs(stock_value_difference / actual_qty)" @@ -564,6 +577,7 @@ def get_return_against_item_fields(voucher_type): "Purchase Invoice": "purchase_invoice_item", "Delivery Note": "dn_detail", "Sales Invoice": "sales_invoice_item", + "Subcontracting Receipt": "subcontracting_receipt_item", } return return_against_item_fields[voucher_type] diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index b98f979c66..87a19a1bf8 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -76,7 +76,7 @@ frappe.ui.form.on('Subcontracting Receipt', { }, __("View")); } - if (!frm.doc.is_return && frm.doc.docstatus == 1) { + if (!frm.doc.is_return && frm.doc.docstatus == 1 && frm.doc.per_returned < 100) { frm.add_custom_button('Subcontract Return', function () { frappe.model.open_mapped_doc({ method: 'erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return', diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 8680311c79..dd1790289a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -21,6 +21,7 @@ from erpnext.controllers.tests.test_subcontracting_controller import ( set_backflush_based_on, ) from erpnext.stock.doctype.item.test_item import make_item +from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import make_subcontracting_receipt @@ -272,6 +273,64 @@ class TestSubcontractingReceipt(FrappeTestCase): for row in scr.supplied_items: self.assertEqual(transferred_batch.get(row.batch_no), row.consumed_qty) + def test_subcontracting_order_partial_return(self): + sco = get_subcontracting_order() + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + scr1 = make_subcontracting_receipt(sco.name) + scr1.save() + scr1.submit() + + scr1_return = make_return_subcontracting_receipt(scr_name=scr1.name, qty=-3) + scr1.load_from_db() + self.assertEqual(scr1_return.status, "Return") + self.assertEqual(scr1.items[0].returned_qty, 3) + + scr2_return = make_return_subcontracting_receipt(scr_name=scr1.name, qty=-7) + scr1.load_from_db() + self.assertEqual(scr2_return.status, "Return") + self.assertEqual(scr1.status, "Return Issued") + self.assertEqual(scr1.items[0].returned_qty, 10) + + def test_subcontracting_order_over_return(self): + sco = get_subcontracting_order() + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + scr1 = make_subcontracting_receipt(sco.name) + scr1.save() + scr1.submit() + + from erpnext.controllers.status_updater import OverAllowanceError + args = frappe._dict(scr_name=scr1.name, qty=-15) + self.assertRaises(OverAllowanceError, make_return_subcontracting_receipt, **args) + + +def make_return_subcontracting_receipt(**args): + args = frappe._dict(args) + return_doc = make_return_doc("Subcontracting Receipt", args.scr_name) + return_doc.supplier_warehouse = args.supplier_warehouse or args.warehouse or "_Test Warehouse 1 - _TC" + + if args.qty: + for item in return_doc.items: + item.qty = args.qty + + if not args.do_not_save: + return_doc.save() + if not args.do_not_submit: + return_doc.submit() + + return_doc.load_from_db() + return return_doc def get_items(**args): args = frappe._dict(args) From 30c56bfe80e505a9e16a959ee148c416c516ec8f Mon Sep 17 00:00:00 2001 From: Vladislav Date: Sun, 22 May 2022 14:04:24 +0300 Subject: [PATCH 32/98] Update ru.csv --- erpnext/translations/ru.csv | 158 ++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 2ee5b3ec7e..6b766e7dc0 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -290,7 +290,7 @@ Assign,Назначить, Assign Salary Structure,Назначить структуру заработной платы, Assign To,Назначить в, Assign to Employees,Назначить сотрудникам, -Assigning Structures...,Назначение структур ..., +Assigning Structures...,Назначение структур..., Associate,Помощник, At least one mode of payment is required for POS invoice.,По крайней мере один способ оплаты требуется для POS счета., Atleast one item should be entered with negative quantity in return document,Как минимум один продукт должен быть введен с отрицательным количеством в возвратном документе, @@ -702,12 +702,12 @@ Credit Account,Кредитный счет, Credit Balance,Кредитный баланс, Credit Card,Кредитная карта, Credit Days cannot be a negative number,Кредитные дни не могут быть отрицательным числом, -Credit Limit,{0}{/0} {1}Кредитный лимит {/1}, -Credit Note,Кредитная запись , +Credit Limit,Кредитный лимит, +Credit Note,Кредитная запись, Credit Note Amount,Сумма кредитной записи, Credit Note Issued,Кредит выдается справка, Credit Note {0} has been created automatically,Кредитная запись {0} была создана автоматически, -Credit limit has been crossed for customer {0} ({1}/{2}),Кредитный лимит был скрещен для клиента {0} ({1} / {2}), +Credit limit has been crossed for customer {0} ({1}/{2}),Кредитный лимит был скрещен для клиента {0} ({1}/{2}), Creditors,Кредиторы, Criteria weights must add up to 100%,Критерии веса должны составлять до 100%, Crop Cycle,Цикл урожая, @@ -892,7 +892,7 @@ Duplicate {0} found in the table,Дубликат {0} найден в табли Duration in Days,Продолжительность в днях, Duties and Taxes,Пошлины и налоги, E-Invoicing Information Missing,Отсутствует информация об инвойсировании, -ERPNext Demo,ERPNext Demo, +ERPNext Demo,ERPNext демо, ERPNext Settings,Настройки ERPNext, Earliest,Самый ранний, Earnest Money,Задаток, @@ -904,7 +904,7 @@ Education,образование, Either location or employee must be required,"Требуется либо место, либо сотрудник", Either target qty or target amount is mandatory,Либо целевой Количество или целевое количество является обязательным, Either target qty or target amount is mandatory.,Либо целевой Количество или целевое количество является обязательным., -Electrical,электрический, +Electrical,Электрический, Electronic Equipments,Электронные приборы, Electronics,Электроника, Eligible ITC,Соответствующий ITC, @@ -1417,8 +1417,8 @@ Label,Ярлык, Laboratory,Лаборатория, Language Name,Название языка, Large,Большой, -Last Communication,Последнее сообщение, -Last Communication Date,Дата последнего общения, +Last Communication,Последняя коммуникация, +Last Communication Date,Дата последней коммуникации, Last Name,Фамилия, Last Order Amount,Последняя сумма заказа, Last Order Date,Последняя дата заказа, @@ -1449,12 +1449,12 @@ Leave and Attendance,Оставить и посещаемость, Leave application {0} already exists against the student {1},Оставить заявку {0} уже существует против ученика {1}, "Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}","Оставить не могут быть выделены, прежде чем {0}, а отпуск баланс уже переноса направляются в будущем записи распределения отпуска {1}", "Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}","Оставьте не могут быть применены / отменены, прежде чем {0}, а отпуск баланс уже переноса направляются в будущем записи распределения отпуска {1}", -Leave of type {0} cannot be longer than {1},"Оставить типа {0} не может быть больше, чем {1}", -Leaves,Листья, +Leave of type {0} cannot be longer than {1},"Отпусков типа {0} не может быть больше, чем {1}", +Leaves,Отпуски, Leaves Allocated Successfully for {0},Отпуск успешно распределен для {0}, -Leaves has been granted sucessfully,Листья были успешно предоставлены, -Leaves must be allocated in multiples of 0.5,"Листья должны быть выделены несколько 0,5", -Leaves per Year,Листья в год, +Leaves has been granted sucessfully,Отпуска были успешно предоставлены, +Leaves must be allocated in multiples of 0.5,"Отпуска должны быть распределены кратно 0,5", +Leaves per Year,Отпусков в год, Ledger,Регистр, Legal,Легальный, Legal Expenses,Судебные издержки, @@ -1480,7 +1480,7 @@ Loans (Liabilities),Кредиты (обязательства), Loans and Advances (Assets),Кредиты и авансы (активы), Local,Локальные, Log,Запись в журнале, -Logs for maintaining sms delivery status,Журналы для просмотра статуса доставки СМС, +Logs for maintaining sms delivery status,Журналы для просмотра статуса доставки SMS, Lost,Поражений, Lost Reasons,Потерянные причины, Low,Низкий, @@ -1514,7 +1514,7 @@ Manage Customer Group Tree.,Управление деревом групп по Manage Sales Partners.,Управление партнерами по сбыту., Manage Sales Person Tree.,Управление деревом менеджеров по продажам., Manage Territory Tree.,Управление деревом территорий., -Manage your orders,Управляйте свои заказы, +Manage your orders,Управление вашими заказами, Management,Менеджмент, Manager,Менеджер, Managing Projects,Управление проектами, @@ -2122,7 +2122,7 @@ Point of Sale,Точки продаж, Point-of-Sale,Торговая точка, Point-of-Sale Profile,Точка-в-продажи профиля, Portal,Портал, -Portal Settings,портал Настройки, +Portal Settings,Настройки портала, Possible Supplier,Возможный поставщик, Postal Expenses,Почтовые расходы, Posting Date,Дата публикации, @@ -2185,7 +2185,7 @@ Produced Qty,Произведенное количество, Product,Продукт, Product Bundle,Продуктовый набор, Product Search,Поиск продукта, -Production,производство, +Production,Производство, Production Item,Производство товара, Products,Продукты, Profit and Loss,Прибыль и убытки, @@ -2296,7 +2296,7 @@ Raw Materials,Сырье, Raw Materials cannot be blank.,Сырье не может быть пустым., Re-open,Снова откройте, Read blog,Читать блог, -Read the ERPNext Manual,Прочитайте Руководство ERPNext, +Read the ERPNext Manual,Прочитайте руководство ERPNext, Reading Uploaded File,Чтение загруженного файла, Real Estate,Недвижимость, Reason For Putting On Hold,Причина удержания, @@ -2429,37 +2429,37 @@ Row # {0}: Cannot return more than {1} for Item {2},Строка # {0}: Нево Row # {0}: Rate cannot be greater than the rate used in {1} {2},"Строка # {0}: ставка не может быть больше ставки, используемой в {1} {2}", Row # {0}: Serial No is mandatory,Строка # {0}: Серийный номер является обязательным, Row # {0}: Serial No {1} does not match with {2} {3},"Строка # {0}: Серийный номер {1}, не соответствует {2} {3}", -Row #{0} (Payment Table): Amount must be negative,Строка # {0} (таблица платежей): сумма должна быть отрицательной, -Row #{0} (Payment Table): Amount must be positive,Строка # {0} (таблица платежей): сумма должна быть положительной, -Row #{0}: Account {1} does not belong to company {2},Строка # {0}: Счет {1} не принадлежит компании {2}, -Row #{0}: Allocated Amount cannot be greater than outstanding amount.,Строка # {0}: выделенная сумма не может превышать невыплаченную сумму., -"Row #{0}: Asset {1} cannot be submitted, it is already {2}","Строка # {0}: Актив {1} не может быть проведен, он уже {2}", -Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.,"Строка # {0}: не может установить значение скорости, если сумма превышает сумму выставленного счета за элемент {1}.", -Row #{0}: Clearance date {1} cannot be before Cheque Date {2},Строка # {0}: дате зазора {1} не может быть до того Cheque Дата {2}, -Row #{0}: Duplicate entry in References {1} {2},Строка # {0}: Дублирующая запись в ссылках {1} {2}, -Row #{0}: Expected Delivery Date cannot be before Purchase Order Date,Строка # {0}: ожидаемая дата поставки не может быть до даты заказа на поставку, -Row #{0}: Item added,Строка № {0}: пункт добавлен, -Row #{0}: Journal Entry {1} does not have account {2} or already matched against another voucher,Строка # {0}: Запись в журнале {1} не имеет учетной записи {2} или уже сопоставляется с другой купон, -Row #{0}: Not allowed to change Supplier as Purchase Order already exists,Строка # {0}: Не разрешено изменять поставщика когда уже существует заказ, -Row #{0}: Please set reorder quantity,"Строка # {0}: Пожалуйста, укажите количество повторного заказа", -Row #{0}: Please specify Serial No for Item {1},"Строка # {0}: Пожалуйста, сформулируйте серийный номер для продукта {1}", -Row #{0}: Qty increased by 1,Строка № {0}: кол-во увеличено на 1, -Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ,"Строка # {0}: цена должна быть такой же, как {1}: {2} ({3} / {4})", -Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry,Строка # {0}: Тип ссылочного документа должен быть одним из заголовка расхода или записи журнала, -"Row #{0}: Reference Document Type must be one of Purchase Order, Purchase Invoice or Journal Entry","Строка # {0}: Тип справочного документа должен быть одним из следующих: Заказ на покупку, Счет-фактура на покупку или Запись в журнале", -Row #{0}: Rejected Qty can not be entered in Purchase Return,Строка # {0}: Отклоненное количество не может быть введено в возврат покупки, -Row #{0}: Rejected Warehouse is mandatory against rejected Item {1},Строка # {0}: Отклонено Склад является обязательным в отношении отклонил Пункт {1}, -Row #{0}: Reqd by Date cannot be before Transaction Date,Строка # {0}: Reqd by Date не может быть до даты транзакции, -Row #{0}: Set Supplier for item {1},Строка # {0}: Установить поставщика для {1}, -Row #{0}: Status must be {1} for Invoice Discounting {2},Строка # {0}: статус должен быть {1} для дисконтирования счета-фактуры {2}, +Row #{0} (Payment Table): Amount must be negative,Строка #{0} (таблица платежей): сумма должна быть отрицательной, +Row #{0} (Payment Table): Amount must be positive,Строка #{0} (таблица платежей): сумма должна быть положительной, +Row #{0}: Account {1} does not belong to company {2},Строка #{0}: Счет {1} не принадлежит компании {2}, +Row #{0}: Allocated Amount cannot be greater than outstanding amount.,Строка #{0}: выделенная сумма не может превышать невыплаченную сумму., +"Row #{0}: Asset {1} cannot be submitted, it is already {2}","Строка #{0}: Актив {1} не может быть проведен, он уже {2}", +Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.,"Строка #{0}: не может установить значение скорости, если сумма превышает сумму выставленного счета за элемент {1}.", +Row #{0}: Clearance date {1} cannot be before Cheque Date {2},Строка #{0}: дате зазора {1} не может быть до того Cheque Дата {2}, +Row #{0}: Duplicate entry in References {1} {2},Строка #{0}: Дублирующая запись в ссылках {1} {2}, +Row #{0}: Expected Delivery Date cannot be before Purchase Order Date,Строка #{0}: ожидаемая дата поставки не может быть до даты заказа на поставку, +Row #{0}: Item added,Строка #{0}: пункт добавлен, +Row #{0}: Journal Entry {1} does not have account {2} or already matched against another voucher,Строка #{0}: Запись в журнале {1} не имеет учетной записи {2} или уже сопоставляется с другой купон, +Row #{0}: Not allowed to change Supplier as Purchase Order already exists,Строка #{0}: Не разрешено изменять поставщика когда уже существует заказ, +Row #{0}: Please set reorder quantity,"Строка #{0}: Пожалуйста, укажите количество повторных заказов", +Row #{0}: Please specify Serial No for Item {1},"Строка #{0}: Пожалуйста, сформулируйте серийный номер для продукта {1}", +Row #{0}: Qty increased by 1,Строка #{0}: кол-во увеличено на 1, +Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ,"Строка #{0}: цена должна быть такой же, как {1}: {2} ({3} / {4})", +Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry,Строка #{0}: Тип ссылочного документа должен быть одним из заголовка расхода или записи журнала, +"Row #{0}: Reference Document Type must be one of Purchase Order, Purchase Invoice or Journal Entry","Строка #{0}: Тип справочного документа должен быть одним из следующих: Заказ на покупку, Счет-фактура на покупку или Запись в журнале", +Row #{0}: Rejected Qty can not be entered in Purchase Return,Строка #{0}: Отклоненное количество не может быть введено в возврат покупки, +Row #{0}: Rejected Warehouse is mandatory against rejected Item {1},Строка #{0}: Отклоненный склад является обязательным для отклоненного продукта {1}, +Row #{0}: Reqd by Date cannot be before Transaction Date,Строка #{0}: Reqd by Date не может быть до даты транзакции, +Row #{0}: Set Supplier for item {1},Строка #{0}: Установить поставщика для {1}, +Row #{0}: Status must be {1} for Invoice Discounting {2},Строка #{0}: статус должен быть {1} для дисконтирования счета-фактуры {2}, "Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches","Строка # {0}: партия {1} имеет только {2} qty. Выберите другой пакет, в котором имеется {3} qty, или разбейте строку на несколько строк, чтобы доставлять / выпускать из нескольких партий", -Row #{0}: Timings conflicts with row {1},Строка # {0}: Тайминги конфликтуют со строкой {1}, -Row #{0}: {1} can not be negative for item {2},Строка # {0}: {1} не может быть отрицательным для {2}, -Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2},"Строка № {0}: Сумма не может быть больше, чем указанная в Авансовом Отчете {1}. Указанная сумма {2}", +Row #{0}: Timings conflicts with row {1},Строка #{0}: Тайминги конфликтуют со строкой {1}, +Row #{0}: {1} can not be negative for item {2},Строка #{0}: {1} не может быть отрицательным для {2}, +Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2},"Строка #{0}: Сумма не может быть больше, чем указанная в Авансовом Отчете {1}. Указанная сумма {2}", Row {0} : Operation is required against the raw material item {1},Строка {0}: требуется операция против элемента исходного материала {1}, -Row {0}# Allocated amount {1} cannot be greater than unclaimed amount {2},"Строка {0} # Выделенная сумма {1} не может быть больше, чем невостребованная сумма {2}", -Row {0}# Item {1} cannot be transferred more than {2} against Purchase Order {3},Строка {0} # Элемент {1} не может быть передан более {2} в отношении заказа на поставку {3}, -Row {0}# Paid Amount cannot be greater than requested advance amount,Строка {0} # Платная сумма не может быть больше запрашиваемой суммы аванса, +Row {0}# Allocated amount {1} cannot be greater than unclaimed amount {2},"Строка {0}# Выделенная сумма {1} не может быть больше, чем невостребованная сумма {2}", +Row {0}# Item {1} cannot be transferred more than {2} against Purchase Order {3},Строка {0}# Продукт {1} не может быть передан более {2} в отношении заказа на поставку {3}, +Row {0}# Paid Amount cannot be greater than requested advance amount,Строка {0}# Оплаченная сумма не может быть больше запрошенной суммы аванса, Row {0}: Activity Type is mandatory.,Строка {0}: Вид деятельности является обязательным., Row {0}: Advance against Customer must be credit,Строка {0}: Аванс в отношении клиента должен быть кредитом, Row {0}: Advance against Supplier must be debit,Строка {0}: Аванс в отношении поставщика должны быть дебетом, @@ -2695,7 +2695,7 @@ Settings for website homepage,Настройки для сайта домашн Settings for website product listing,Настройки для списка товаров на сайте, Settled,Установившаяся, Setup Gateway accounts.,Настройка шлюза счета., -Setup SMS gateway settings,Указать настройки СМС-шлюза, +Setup SMS gateway settings,Указать настройки SMS-шлюза, Setup cheque dimensions for printing,Размеры Проверьте настройки для печати, Setup default values for POS Invoices,Настройка значений по умолчанию для счетов POS, Setup mode of POS (Online / Offline),Режим настройки POS (Online / Offline), @@ -3043,10 +3043,10 @@ To GSTIN,К GSTIN, To Party Name,Название партии, To Pin Code,К PIN-коду, To Place,Положить, -To Receive,Получить, +To Receive,К получению, To Receive and Bill,Для приема и Билл, To State,Государство, -To Warehouse,Для Склад, +To Warehouse,Для склада, To create a Payment Request reference document is required,Для создания ссылочного документа запроса платежа требуется, To date can not be equal or less than from date,"На сегодняшний день не может быть равным или меньше, чем с даты", To date can not be less than from date,"На сегодняшний день не может быть меньше, чем с даты", @@ -3081,7 +3081,7 @@ Total Credit/ Debit Amount should be same as linked Journal Entry,"Общая с Total Debit must be equal to Total Credit. The difference is {0},"Всего Дебет должна быть равна общей выработке. Разница в том, {0}", Total Deduction,Общий вычет, Total Invoiced Amount,Общая сумма по счетам, -Total Leaves,Всего Листья, +Total Leaves,Всего отпусков, Total Order Considered,Всего рассмотренных заказов, Total Order Value,Общая стоимость заказа, Total Outgoing,Всего исходящих, @@ -3660,7 +3660,7 @@ Chart,Диаграмма, Choose a corresponding payment,Выберите соответствующий платеж, Click on the link below to verify your email and confirm the appointment,"Нажмите на ссылку ниже, чтобы подтвердить свою электронную почту и подтвердить встречу", Close,Закрыть, -Communication,Общение, +Communication,Коммуникация, Compact Item Print,Компактный товара печати, Company,Организация, Company of asset {0} and purchase document {1} doesn't matches.,Компания актива {0} и документ покупки {1} не совпадают., @@ -3995,25 +3995,25 @@ Review,Обзор, Room,Комната, Room Type,Тип комнаты, Row # ,Строка # , -Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same,Строка # {0}: принятый склад и склад поставщика не могут быть одинаковыми, -Row #{0}: Cannot delete item {1} which has already been billed.,"Строка # {0}: невозможно удалить элемент {1}, для которого уже выставлен счет.", -Row #{0}: Cannot delete item {1} which has already been delivered,"Строка # {0}: невозможно удалить элемент {1}, который уже был доставлен", -Row #{0}: Cannot delete item {1} which has already been received,"Строка # {0}: невозможно удалить элемент {1}, который уже был получен", -Row #{0}: Cannot delete item {1} which has work order assigned to it.,"Строка # {0}: невозможно удалить элемент {1}, которому назначено рабочее задание.", -Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.,"Строка # {0}: невозможно удалить элемент {1}, который назначен заказу клиента на покупку.", -Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor,Строка № {0}: невозможно выбрать склад поставщика при подаче сырья субподрядчику, -Row #{0}: Cost Center {1} does not belong to company {2},Строка # {0}: МВЗ {1} не принадлежит компании {2}, -Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Work Order {3}. Please update operation status via Job Card {4}.,"Строка # {0}: операция {1} не завершена для {2} количества готовой продукции в рабочем задании {3}. Пожалуйста, обновите статус операции с помощью Job Card {4}.", -Row #{0}: Payment document is required to complete the transaction,Строка # {0}: для завершения транзакции требуется платежный документ, -Row #{0}: Serial No {1} does not belong to Batch {2},Строка # {0}: серийный номер {1} не принадлежит партии {2}, -Row #{0}: Service End Date cannot be before Invoice Posting Date,Строка # {0}: дата окончания обслуживания не может быть раньше даты проводки счета, -Row #{0}: Service Start Date cannot be greater than Service End Date,Строка # {0}: дата начала обслуживания не может быть больше даты окончания обслуживания, -Row #{0}: Service Start and End Date is required for deferred accounting,Строка # {0}: дата начала и окончания обслуживания требуется для отложенного учета, +Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same,Строка #{0}: склад для получения и склад поставщика не могут быть одинаковыми, +Row #{0}: Cannot delete item {1} which has already been billed.,"Строка #{0}: невозможно удалить продукт {1}, для которого уже выставлен счет.", +Row #{0}: Cannot delete item {1} which has already been delivered,"Строка #{0}: невозможно удалить продукт {1}, который уже был доставлен", +Row #{0}: Cannot delete item {1} which has already been received,"Строка #{0}: невозможно удалить продукт {1}, который уже был получен", +Row #{0}: Cannot delete item {1} which has work order assigned to it.,"Строка #{0}: невозможно удалить продукт {1}, которому назначено рабочее задание.", +Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.,"Строка #{0}: невозможно удалить продукт {1}, который есть в заказе клиента на покупку.", +Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor,Строка #{0}: невозможно выбрать склад поставщика при подаче сырья субподрядчику, +Row #{0}: Cost Center {1} does not belong to company {2},Строка #{0}: МВЗ {1} не принадлежит компании {2}, +Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Work Order {3}. Please update operation status via Job Card {4}.,"Строка #{0}: операция {1} не завершена для {2} количества готовой продукции в рабочем задании {3}. Пожалуйста, обновите статус операции с помощью Карточки работ {4}.", +Row #{0}: Payment document is required to complete the transaction,Строка #{0}: для завершения транзакции требуется платежный документ, +Row #{0}: Serial No {1} does not belong to Batch {2},Строка #{0}: серийный номер {1} не принадлежит партии {2}, +Row #{0}: Service End Date cannot be before Invoice Posting Date,Строка #{0}: дата окончания обслуживания не может быть раньше даты проводки счета, +Row #{0}: Service Start Date cannot be greater than Service End Date,Строка #{0}: дата начала обслуживания не может быть больше даты окончания обслуживания, +Row #{0}: Service Start and End Date is required for deferred accounting,Строка #{0}: дата начала и окончания обслуживания требуется для отложенного учета, Row {0}: Invalid Item Tax Template for item {1},Строка {0}: неверный шаблон налога на товар для товара {1}, Row {0}: Quantity not available for {4} in warehouse {1} at posting time of the entry ({2} {3}),Строка {0}: количество недоступно для {4} на складе {1} во время проводки записи ({2} {3}), Row {0}: user has not applied the rule {1} on the item {2},Строка {0}: пользователь не применил правило {1} к элементу {2}, Row {0}:Sibling Date of Birth cannot be greater than today.,"Строка {0}: дата рождения родного брата не может быть больше, чем сегодня.", -Row({0}): {1} is already discounted in {2},Строка ({0}): {1} уже дисконтирован в {2}, +Row({0}): {1} is already discounted in {2},Строка({0}): {1} уже дисконтирован в {2}, Rows Added in {0},Строки добавлены в {0}, Rows Removed in {0},Строки удалены в {0}, Sanctioned Amount limit crossed for {0} {1},Предел санкционированной суммы для {0} {1}, @@ -4125,7 +4125,7 @@ Unknown Caller,Неизвестный абонент, Unlink external integrations,Отключить внешние интеграции, Unmarked Attendance for days,Посещаемость без опознавательных знаков в течение нескольких дней, Unpublish Item,Отменить публикацию, -Unreconciled,несверенный, +Unreconciled,Несверенный, Unsupported GST Category for E-Way Bill JSON generation,Неподдерживаемая категория НДС для генерации E-Way Bill JSON, Update,Обновить, Update Details,Обновить данные, @@ -4575,8 +4575,8 @@ Get Payment Entries,Получить Записи оплаты, Payment Entries,Записи оплаты, Update Clearance Date,Обновление просвет Дата, Bank Reconciliation Detail,Подробности банковской сверки, -Cheque Number,Чек Количество, -Cheque Date,Чек Дата, +Cheque Number,Номер чека, +Cheque Date,Дата чека, Statement Header Mapping,Сопоставление заголовков операторов, Statement Headers,Заголовки операторов, Transaction Data Mapping,Сопоставление данных транзакций, @@ -4811,7 +4811,7 @@ Loyalty Program Help,Помощь в программе лояльности, Loyalty Program Collection,Коллекция программы лояльности, Tier Name,Название уровня, Minimum Total Spent,Минимальные общие затраты, -Collection Factor (=1 LP),Коэффициент сбора (= 1 Балл), +Collection Factor (=1 LP),Коэффициент сбора (=1 Балл), For how much spent = 1 Loyalty Point,За сколько потраченных = 1 Балл лояльности, Mode of Payment Account,Форма оплаты счета, Default Account,По умолчанию учетная запись, @@ -5241,7 +5241,7 @@ Grace Period,Льготный период, Number of days after invoice date has elapsed before canceling subscription or marking subscription as unpaid,Количество дней после истечения срока выставления счета перед отменой подписки или подпиской по подписке как неоплаченной, Prorate,пропорциональная доля, Tax Rule,Налоговое положение, -Tax Type,Налоги Тип, +Tax Type,Тип налога, Use for Shopping Cart,Используйте корзину для, Billing City,Город платильщика, Billing County,Платежный County, @@ -5605,13 +5605,13 @@ Ringing,Звонок, Missed,Пропущенный, Call Duration in seconds,Продолжительность звонка в секундах, Recording URL,Запись URL, -Communication Medium,Связь Средний, -Communication Medium Type,Тип средств связи, +Communication Medium,Способ коммуникации, +Communication Medium Type,Тип способа коммуникации, Voice,Голос, Catch All,Поймать все, "If there is no assigned timeslot, then communication will be handled by this group","Если нет назначенного временного интервала, то связь будет обрабатываться этой группой", Timeslots,Временные интервалы, -Communication Medium Timeslot,Коммуникационный средний таймслот, +Communication Medium Timeslot,Коммуникационный таймслот, Employee Group,Группа сотрудников, Appointment,"Деловое свидание, встреча", Scheduled Time,Назначенное время, @@ -5891,7 +5891,7 @@ Grading Basis,Оценка основ, Latest Highest Score,Последний наивысший балл, Latest Attempt,Последняя попытка, Quiz Activity,Викторина, -Enrollment,регистрация, +Enrollment,Регистрация, Pass,Проходить, Quiz Question,Контрольный вопрос, Quiz Result,Результат теста, @@ -5936,9 +5936,9 @@ Application Fee,Регистрационный взнос, Naming Series (for Student Applicant),Идентификация по Имени (для заявителей-студентов), LMS Only,Только LMS, EDU-APP-.YYYY.-,EDU-APP-.YYYY.-, -Application Status,Статус приложения, +Application Status,Статус подачи документов, Application Date,Дата подачи документов, -Student Attendance Tool,Student Участники Инструмент, +Student Attendance Tool,Инструмент посещаемости учащихся, Group Based On,Группа на основе, Students HTML,Студенты HTML, Group Based on,Группа основана на, @@ -9504,7 +9504,7 @@ Invalid Quantity,Неверное количество, {0} on {1},{0} в {1}, {0} with {1},{0} с {1}, Appointment Confirmation Message Not Sent,Сообщение с подтверждением встречи не отправлено, -"SMS not sent, please check SMS Settings","СМС не отправлено, проверьте настройки СМС", +"SMS not sent, please check SMS Settings","SMS не отправлено, проверьте настройки SMS", Healthcare Service Unit Type cannot have both {0} and {1},Тип единицы медицинского обслуживания не может содержать одновременно {0} и {1}, Healthcare Service Unit Type must allow atleast one among {0} and {1},Тип единицы медицинского обслуживания должен допускать хотя бы одно из {0} и {1}, Set Response Time and Resolution Time for Priority {0} in row {1}.,Задайте время ответа и время разрешения для приоритета {0} в строке {1}., From 61296a0658299538c81f4418f580d3e0381e3648 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 26 May 2022 15:08:16 +0530 Subject: [PATCH 33/98] fix: Subcontracting through Production Plan --- .../manufacturing/doctype/production_plan/production_plan.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 9ca05b927f..132e1ebea5 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -506,7 +506,7 @@ class ProductionPlan(Document): po.is_subcontracted = 1 for row in po_list: po_data = { - "item_code": row.production_item, + "fg_item": row.production_item, "warehouse": row.fg_warehouse, "production_plan_sub_assembly_item": row.name, "bom": row.bom_no, @@ -516,9 +516,6 @@ class ProductionPlan(Document): for field in [ "schedule_date", "qty", - "uom", - "stock_uom", - "item_name", "description", "production_plan_item", ]: From ac160d58bd9ce080f689c212aeab26cd766bd2c2 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Sat, 28 May 2022 04:16:46 +0300 Subject: [PATCH 34/98] Update ru.csv --- erpnext/translations/ru.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 6b766e7dc0..7e712f363f 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -2347,7 +2347,7 @@ Remaining,Осталось, Remaining Balance,Остаток средств, Remarks,Примечания, Reminder to update GSTIN Sent,Напоминание об обновлении отправленного GSTIN, -Remove item if charges is not applicable to that item,"Удалить продукт, если сборы не применимы к этому продукту", +Remove item if charges is not applicable to that item,"Удалить объект, если к нему не применяются сборы", Removed items with no change in quantity or value.,Удалены пункты без изменения в количестве или стоимости., Reopen,Возобновить, Reorder Level,Уровень переупорядочения, @@ -7818,7 +7818,7 @@ Phone No,Номер телефона, Company Description,Описание компании, Registration Details,Регистрационные данные, Company registration numbers for your reference. Tax numbers etc.,Регистрационные номера компании для вашей справки. Налоговые числа и т.д., -Delete Company Transactions,Удалить Сделки Компания, +Delete Company Transactions,Удалить транзакции компании, Currency Exchange,Курс обмена валюты, Specify Exchange Rate to convert one currency into another,Укажите Курс конвертировать одну валюту в другую, From Currency,Из валюты, From 832bedc747bb653a504b9aa3cf1a6ad007c9abd2 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Mon, 30 May 2022 12:56:27 +0300 Subject: [PATCH 35/98] Update ru.csv --- erpnext/translations/ru.csv | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 7e712f363f..eda548f315 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -421,7 +421,7 @@ Buildings,Здания, Bundle items at time of sale.,Собирать продукты в момент продажи., Business Development Manager,Менеджер по развитию бизнеса, Buy,Купить, -Buying,Покупки, +Buying,Закупки, Buying Amount,Сумма покупки, Buying Price List,Ценовой список покупок, Buying Rate,Частота покупки, @@ -2509,7 +2509,7 @@ Salary Slip of employee {0} already created for this period,Зарплата С Salary Slip of employee {0} already created for time sheet {1},Зарплата Скольжение работника {0} уже создан для табеля {1}, Salary Slip submitted for period from {0} to {1},"Зарплатная ведомость отправлена за период с {0} по {1}", Salary Structure Assignment for Employee already exists,Присвоение структуры зарплаты сотруднику уже существует, -Salary Structure Missing,Структура заработной платы Отсутствующий, +Salary Structure Missing,Структура заработной платы отсутствует, Salary Structure must be submitted before submission of Tax Ememption Declaration,Структура заработной платы должна быть представлена до подачи декларации об освобождении от налогов, Salary Structure not found for employee {0} and date {1},Структура зарплаты не найдена для сотрудника {0} и даты {1}, Salary Structure should have flexible benefit component(s) to dispense benefit amount,Структура заработной платы должна иметь гибкий компонент (ы) выгоды для распределения суммы пособия, @@ -8285,7 +8285,7 @@ Out of AMC,Из КУА, Warranty Period (Days),Гарантийный срок (дней), Serial No Details,Серийный номер подробнее, MAT-STE-.YYYY.-,MAT-STE-.YYYY.-, -Stock Entry Type,Тип входа, +Stock Entry Type,Тип складской записи, Stock Entry (Outward GIT),Вход в акции (внешний GIT), Material Consumption for Manufacture,Потребление материала для производства, Repack,Перепаковать, @@ -9242,7 +9242,7 @@ Tasks Completed,Задачи выполнены, Tasks Overdue,Просроченные задачи, Completion,Завершение, Provident Fund Deductions,Отчисления в резервный фонд, -Purchase Order Analysis,Анализ заказа на закупку, +Purchase Order Analysis,Анализ заказов на закупку, From and To Dates are required.,Укажите даты от и до., To Date cannot be before From Date.,Дата не может быть раньше даты начала., Qty to Bill,Кол-во к счету, @@ -9267,7 +9267,7 @@ Sales Order Analysis,Анализ заказов на продажу, Amount Delivered,Сумма доставки, Delay (in Days),Задержка (в днях), Group by Sales Order,Группировать по заказу на продажу, - Sales Value,Объем продаж, + Sales Value, Объем продаж, Stock Qty vs Serial No Count,Кол-во на складе по сравнению с серийным номером, Serial No Count,Серийный номер, Work Order Summary,Сводка заказа на работу, @@ -9320,8 +9320,8 @@ Error creating membership entry for {0},Ошибка создания запис A customer is already linked to this Member,Клиент уже привязан к этому участнику, End Date must not be lesser than Start Date,Дата окончания не должна быть меньше даты начала., Employee {0} already has Active Shift {1}: {2},Сотрудник {0} уже имеет активную смену {1}: {2}, - from {0},от {0}, - to {0},в {0}, + from {0}, от {0}, + to {0}, в {0}, Please select Employee first.,"Пожалуйста, сначала выберите сотрудника.", Please set {0} for the Employee or for Department: {1},Установите {0} для сотрудника или отдела: {1}, To Date should be greater than From Date,"Дата до должна быть больше, чем Дата", @@ -9838,3 +9838,8 @@ Enable European Access,Включить европейский доступ, Creating Purchase Order ...,Создание заказа на поставку ..., "Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Выберите поставщика из списка поставщиков по умолчанию для позиций ниже. При выборе Заказ на поставку будет сделан в отношении товаров, принадлежащих только выбранному Поставщику.", Row #{}: You must select {} serial numbers for item {}.,Строка № {}: необходимо выбрать {} серийных номеров для позиции {}., +Items & Pricing,Продукты и цены, +Overdue,Просрочено, +Completed,Завершенно, +Total Tasks,Всего задач, +Build,Конструктор, From 105dbad47ce89076af80a2f0f8921c81c86e3654 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Tue, 31 May 2022 03:06:28 +0300 Subject: [PATCH 36/98] Update ru.csv --- erpnext/translations/ru.csv | 86 ++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index eda548f315..3e169fbde7 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -1287,12 +1287,12 @@ Installing presets,Установка пресетов, Institute Abbreviation,институт Аббревиатура, Institute Name,Название института, Instructor,Инструктор, -Insufficient Stock,Недостаточный Stock, -Insurance Start date should be less than Insurance End date,"Дата страхование начала должна быть меньше, чем дата страхование End", +Insufficient Stock,Недостаточный запас, +Insurance Start date should be less than Insurance End date,"Дата начала страхования должна быть раньше, чем дата окончания", Integrated Tax,Интегрированный налог, Inter-State Supplies,Межгосударственные поставки, -Interest Amount,Проценты Сумма, -Interests,интересы, +Interest Amount,Сумма процентов, +Interests,Интересы, Intern,Стажер, Internet Publishing,Интернет издания, Intra-State Supplies,Внутригосударственные поставки, @@ -1925,7 +1925,7 @@ Pending Amount,В ожидании Сумма, Pending Leaves,Ожидающие листья, Pending Qty,В ожидании кол-во, Pending Quantity,Количество в ожидании, -Pending Review,В ожидании отзыв, +Pending Review,В ожидании отзыва, Pending activities for today,В ожидании деятельность на сегодняшний день, Pension Funds,Пенсионные фонды, Percentage Allocation should be equal to 100%,Процент Распределение должно быть равно 100%, @@ -1949,7 +1949,7 @@ Planned Qty,Планируемое кол-во, Planning,Планирование, Plants and Machineries,Растения и Механизмов, Please Set Supplier Group in Buying Settings.,Установите группу поставщиков в разделе «Настройки покупок»., -Please add a Temporary Opening account in Chart of Accounts,"Пожалуйста, добавьте временный вступительный счет в План счетов", +Please add a Temporary Opening account in Chart of Accounts,"Пожалуйста, добавьте временный вступительный счет в план счетов", Please add the account to root level Company - ,"Пожалуйста, добавьте счет на корневой уровень компании -", Please add the remaining benefits {0} to any of the existing component,Добавьте оставшиеся преимущества {0} к любому из существующих компонентов, Please check Multi Currency option to allow accounts with other currency,"Пожалуйста, проверьте мультивалютный вариант, позволяющий счета другой валюте", @@ -2146,7 +2146,7 @@ Preview Salary Slip,Просмотр Зарплата скольжению, Previous Financial Year is not closed,Предыдущий финансовый год не закрыт, Price,Цена, Price List,Прайс-лист, -Price List Currency not selected,Прайс-лист Обмен не выбран, +Price List Currency not selected,Валюта прайс-листа не выбрана, Price List Rate,Прайс-лист Оценить, Price List master.,Мастер Прайс-лист., Price List must be applicable for Buying or Selling,Прайс-лист должен быть применим для покупки или продажи, @@ -2701,10 +2701,10 @@ Setup default values for POS Invoices,Настройка значений по Setup mode of POS (Online / Offline),Режим настройки POS (Online / Offline), Setup your Institute in ERPNext,Установите свой институт в ERPNext, Share Balance,Баланс акций, -Share Ledger,Поделиться записями, +Share Ledger,Записи по акциям, Share Management,Управление долями, Share Transfer,Передача акций, -Share Type,Share Тип, +Share Type,Тип акций, Shareholder,Акционер, Ship To State,Корабль в штат, Shipments,Поставки, @@ -2796,8 +2796,8 @@ Stock Entry {0} is not submitted,Складской акт {0} не провед Stock Expenses,Расходы по Запасам, Stock In Hand,Запасы на руках, Stock Items,Позиции на складе, -Stock Ledger,Книга учета Запасов, -Stock Ledger Entries and GL Entries are reposted for the selected Purchase Receipts,Записи складской книги и записи GL запасов отправляются для выбранных покупок, +Stock Ledger,Книга учета запасов, +Stock Ledger Entries and GL Entries are reposted for the selected Purchase Receipts,Записи книги учета запасов и записи GL повторно публикуются для выбранных квитанций о покупках, Stock Levels,Уровень запасов, Stock Liabilities,Обязательства по запасам, Stock Options,Опционы, @@ -2829,9 +2829,9 @@ Student Email ID,Идентификация студента по электро Student Group,Учебная группа, Student Group Strength,Сила студенческой группы, Student Group is already updated.,Студенческая группа уже обновлена., -Student Group: ,Студенческая группа:, +Student Group: ,Студенческая группа: , Student ID,Студенческий билет, -Student ID: ,Студенческий билет:, +Student ID: ,Студенческий билет: , Student LMS Activity,Студенческая LMS Активность, Student Mobile No.,Мобильный номер студента, Student Name,Имя ученика, @@ -2864,9 +2864,9 @@ Successfully created payment entries,Успешно созданные плат Successfully deleted all transactions related to this company!,"Успешно удален все сделки, связанные с этой компанией!", Sum of Scores of Assessment Criteria needs to be {0}.,Сумма десятков критериев оценки должно быть {0}., Sum of points for all goals should be 100. It is {0},Сумма баллов за все цели должны быть 100. Это {0}, -Summary,Резюме, -Summary for this month and pending activities,Резюме для этого месяца и в ожидании деятельности, -Summary for this week and pending activities,Резюме на этой неделе и в ожидании деятельности, +Summary,Сводка, +Summary for this month and pending activities,Сводка за этот месяц и предстоящие мероприятия, +Summary for this week and pending activities,Сводка за эту неделю и предстоящие мероприятия, Sunday,Воскресенье, Suplier,Поставщик, Supplier,Поставщик, @@ -2880,7 +2880,7 @@ Supplier Name,наименование поставщика, Supplier Part No,Деталь поставщика №, Supplier Quotation,Предложение поставщика, Supplier Scorecard,Оценочная карта поставщика, -Supplier Warehouse mandatory for sub-contracted Purchase Receipt,Поставщик Склад обязательным для субподрядчиком ТОВАРНЫЙ ЧЕК, +Supplier Warehouse mandatory for sub-contracted Purchase Receipt,Наличие склада поставщика обязательно для субподрядной квитанции о покупке, Supplier database.,База данных поставщиков., Supplier {0} not found in {1},Поставщик {0} не найден в {1}, Supplier(s),Поставщик(и), @@ -3243,7 +3243,7 @@ View Fees Records,Посмотреть рекорды, View Form,Посмотреть форму, View Lab Tests,Просмотр лабораторных тестов, View Leads,Посмотреть лиды, -View Ledger,Посмотреть Леджер, +View Ledger,Посмотреть записи, View Now,Просмотр сейчас, View a list of all the help videos,Просмотреть список всех справочных видео, View in Cart,Смотрите в корзину, @@ -3314,7 +3314,7 @@ Work Orders Created: {0},Созданы рабочие задания: {0}, Work Summary for {0},Резюме работы для {0}, Work-in-Progress Warehouse is required before Submit,Работа-в-Прогресс Склад требуется перед Отправить, Workflow,Рабочий процесс, -Working,Работающий, +Working,В работе, Working Hours,Часы работы, Workstation,Рабочее место, Workstation is closed on the following dates as per Holiday List: {0},Рабочая место закрыто в следующие даты согласно списка праздников: {0}, @@ -3869,7 +3869,7 @@ Non stock items,Нет на складе, Not Allowed,Не разрешено, Not allowed to create accounting dimension for {0},Не разрешено создавать учетное измерение для {0}, Not permitted. Please disable the Lab Test Template,"Не разрешено Пожалуйста, отключите шаблон лабораторного теста", -Note,Заметки, +Note,Заметка, Notes: ,Заметки: , On Converting Opportunity,О возможности конвертации, On Purchase Order Submission,При подаче заказа на поставку, @@ -4217,7 +4217,7 @@ Mode Of Payment,Способ оплаты, No students Found,Студенты не найдены, Not in Stock,Нет в наличии, Please select a Customer,Выберите клиента, -Printed On,Отпечатано на, +Printed On,Напечатано на, Received From,Получено от, Sales Person,Продавец, To date cannot be before From date,На сегодняшний день не может быть раньше От даты, @@ -5385,18 +5385,18 @@ Insurance Start Date,Дата начала страхования, Insurance End Date,Дата окончания страхования, Comprehensive Insurance,Комплексное страхование, Maintenance Required,Требуется техническое обслуживание, -Check if Asset requires Preventive Maintenance or Calibration,"Проверьте, требуется ли Asset профилактическое обслуживание или калибровка", +Check if Asset requires Preventive Maintenance or Calibration,"Проверьте, требует ли актив профилактического обслуживания или калибровки", Booked Fixed Asset,Забронированные основные средства, Purchase Receipt Amount,Сумма покупки, Default Finance Book,Финансовая книга по умолчанию, Quality Manager,Менеджер по качеству, -Asset Category Name,Asset Категория Название, +Asset Category Name,Название категории активов, Depreciation Options,Варианты амортизации, Enable Capital Work in Progress Accounting,Включить капитальную работу в процессе учета, Finance Book Detail,Финансовая книга, Asset Category Account,Счет категории активов, Fixed Asset Account,Счет учета основных средств, -Accumulated Depreciation Account,Начисленной амортизации Счет, +Accumulated Depreciation Account,Счет накопленной амортизации, Depreciation Expense Account,Износ счет расходов, Capital Work In Progress Account,Счет капитальной работы, Asset Finance Book,Финансовая книга по активам, @@ -5441,7 +5441,7 @@ Failure Date,Дата отказа, Assign To Name,Назначить имя, Repair Status,Статус ремонта, Error Description,Описание ошибки, -Downtime,время простоя, +Downtime,Время простоя, Repair Cost,Стоимость ремонта, Manufacturing Manager,Менеджер производства, Current Asset Value,Текущая стоимость актива, @@ -6073,7 +6073,7 @@ Shopify Tax/Shipping Title,Изменить название налога / до ERPNext Account,Учетная запись ERPNext, Shopify Webhook Detail,Узнайте подробности веб-камеры, Webhook ID,Идентификатор Webhook, -Tally Migration,Tally Migration, +Tally Migration,Tally миграция, Master Data,Основные данные, "Data exported from Tally that consists of the Chart of Accounts, Customers, Suppliers, Addresses, Items and UOMs","Данные, экспортированные из Tally, которые состоят из плана счетов, клиентов, поставщиков, адресов, позиций и единиц измерения", Is Master Data Processed,Обработка основных данных, @@ -6082,7 +6082,7 @@ Tally Creditors Account,Счет Tally Creditors, Creditors Account set in Tally,Счет кредиторов установлен в Tally, Tally Debtors Account,Счет Tally должников, Debtors Account set in Tally,Счет дебитора установлен в Tally, -Tally Company,Талли Компания, +Tally Company,Tally Компания, Company Name as per Imported Tally Data,Название компании согласно импортированным данным подсчета, Default UOM,Единица измерения по умолчанию, UOM in case unspecified in imported data,"Единицы измерения, если они не указаны в импортированных данных", @@ -6108,7 +6108,7 @@ Freight and Forwarding Account,Фрахт и пересылка, Creation User,Создание пользователя, "The user that will be used to create Customers, Items and Sales Orders. This user should have the relevant permissions.","Пользователь, который будет использоваться для создания клиентов, товаров и заказов на продажу. Этот пользователь должен иметь соответствующие разрешения.", "This warehouse will be used to create Sales Orders. The fallback warehouse is ""Stores"".",Этот склад будет использоваться для создания заказов на продажу. Резервный склад "Магазины"., -"The fallback series is ""SO-WOO-"".",Аварийная серия "SO-WOO-"., +"The fallback series is ""SO-WOO-"".","Аварийная серия ""SO-WOO-"".", This company will be used to create Sales Orders.,Эта компания будет использоваться для создания заказов на продажу., Delivery After (Days),Доставка после (дней), This is the default offset (days) for the Delivery Date in Sales Orders. The fallback offset is 7 days from the order placement date.,Это смещение по умолчанию (дни) для даты поставки в заказах на продажу. Смещение отступления составляет 7 дней с даты размещения заказа., @@ -6455,7 +6455,7 @@ Appraisal Goal,Цель оценки, Key Responsibility Area,Основная зона ответственности, Weightage (%),Весовая нагрузка (%), Score (0-5),Оценка (0-5), -Score Earned,Оценка Заработано, +Score Earned,Оценка получена, Appraisal Template Title,Название шаблона оценки, Appraisal Template Goal,Цель шаблона оценки, KRA,КРА, @@ -6747,7 +6747,7 @@ Applicant Email Address,Адрес электронной почты заяви Awaiting Response,В ожидании ответа, Job Offer Terms,Условия работы, Select Terms and Conditions,Выберите Сроки и условия, -Printing Details,Печатать Подробности, +Printing Details,Подробности печати, Job Offer Term,Срок действия предложения, Offer Term,Условие предложения, Value / Description,Значение / Описание, @@ -7645,7 +7645,7 @@ Campaign Schedules,Расписание кампаний, Buyer of Goods and Services.,Покупатель товаров и услуг., CUST-.YYYY.-,CUST-.YYYY.-, Default Company Bank Account,Стандартный банковский счет компании, -From Lead,Из Лида, +From Lead,Из лида, Account Manager,Менеджер по работе с клиентами, Allow Sales Invoice Creation Without Sales Order,Разрешить создание счета без заказа на продажу, Allow Sales Invoice Creation Without Delivery Note,Разрешить создание счета без накладной, @@ -7825,7 +7825,7 @@ From Currency,Из валюты, To Currency,В валюту, For Buying,Для покупки, For Selling,Для продажи, -Customer Group Name,Группа Имя клиента, +Customer Group Name,Название группы клиентов, Parent Customer Group,Родительская группа клиента, Only leaf nodes are allowed in transaction,Только листовые узлы допускаются в сделке, Mention if non-standard receivable account applicable,Упоминание если нестандартная задолженность счет применимо, @@ -7893,7 +7893,7 @@ This is the number of the last created transaction with this prefix,Это чи Update Series Number,Обновить Идентификаторы по Номеру, Quotation Lost Reason,Причина Отказа от Предложения, A third party distributor / dealer / commission agent / affiliate / reseller who sells the companies products for a commission.,"Сторонний дистрибьютер, дилер, агент, филиал или реселлер, который продаёт продукты компании за комиссионное вознаграждение.", -Sales Partner Name,Имя Партнера по продажам, +Sales Partner Name,Имя партнера по продажам, Partner Type,Тип партнера, Address & Contacts,Адрес и контакты, Address Desc,Адрес по убыванию, @@ -7914,7 +7914,7 @@ Sales Person Targets,Цели продавца, Set targets Item Group-wise for this Sales Person.,Задайте цели Продуктовых Групп для Продавца, Supplier Group Name,Название группы поставщиков, Parent Supplier Group,Родительская группа поставщиков, -Target Detail,Цель Подробности, +Target Detail,Подробности цели, Target Qty,Целевое количество, Target Amount,Целевая сумма, Target Distribution,Распределение цели, @@ -7973,13 +7973,13 @@ Is Return,Является Вернуться, Issue Credit Note,Кредитная кредитная карта, Return Against Delivery Note,Вернуться На накладной, Customer's Purchase Order No,Клиентам Заказ Нет, -Billing Address Name,Адрес для выставления счета Имя, +Billing Address Name,Название адреса для выставления счета, Required only for sample item.,Требуется только для образца пункта., "If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Если вы создали стандартный шаблон в шаблонах Налоги с налогами и сбором платежей, выберите его и нажмите кнопку ниже.", In Words will be visible once you save the Delivery Note.,По словам будет виден только вы сохраните накладной., In Words (Export) will be visible once you save the Delivery Note.,В Слов (Экспорт) будут видны только вы сохраните накладной., Transporter Info,Информация для транспортировки, -Driver Name,Имя драйвера, +Driver Name,Имя водителя, Track this Delivery Note against any Project,Подписка на Delivery Note против любого проекта, Inter Company Reference,Справочник Интер, Print Without Amount,Распечатать без суммы, @@ -8079,7 +8079,7 @@ Delivered by Supplier (Drop Ship),Доставка поставщиком, Supplier Items,Продукты поставщика, Foreign Trade Details,Сведения о внешней торговле, Country of Origin,Страна происхождения, -Sales Details,Продажи Подробности, +Sales Details,Детали продажи, Default Sales Unit of Measure,Единица измерения продаж по умолчанию, Is Sales Item,Продаваемый продукт, Max Discount (%),Макс. скидка (%), @@ -8117,7 +8117,7 @@ Item Alternative,Альтернативный продукт, Alternative Item Code,Альтернативный код продукта, Two-way,Двусторонний, Alternative Item Name,Альтернативное название продукта, -Attribute Name,Имя атрибута, +Attribute Name,Название атрибута, Numeric Values,Числовые значения, From Range,От хребта, Increment,Приращение, @@ -8236,8 +8236,8 @@ Transporter Details,Детали транспорта, Vehicle Number,Номер транспортного средства, Vehicle Date,Дата транспортного средства, Received and Accepted,Получил и принял, -Accepted Quantity,Принято Количество, -Rejected Quantity,Отклонен Количество, +Accepted Quantity,Количество принятых, +Rejected Quantity,Количество отклоненных, Accepted Qty as per Stock UOM,Принятое количество в соответствии с единицами измерения запаса, Sample Quantity,Количество образцов, Rate and Amount,Ставку и сумму, @@ -8447,7 +8447,7 @@ No of Sent SMS,Кол-во отправленных SMS, Sent To,Отправить, Absent Student Report,Отчет о пропуске занятия, Assessment Plan Status,Статус плана оценки, -Asset Depreciation Ledger,Износ Леджер активов, +Asset Depreciation Ledger,Книга амортизации основных средств, Asset Depreciations and Balances,Активов Амортизация и противовесов, Available Stock for Packing Items,Доступные Запасы для Комплектации Продуктов, Bank Clearance Summary,Банк уплата по счетам итого, @@ -8559,7 +8559,7 @@ Sales Order Trends,Динамика по сделкам, Sales Partner Commission Summary,Сводка комиссий партнеров по продажам, Sales Partner Target Variance based on Item Group,Целевое отклонение партнера по продажам на основе группы товаров, Sales Partner Transaction Summary,Сводка по сделкам с партнерами по продажам, -Sales Partners Commission,Комиссионные Партнеров по продажам, +Sales Partners Commission,Комиссия партнеров по продажам, Invoiced Amount (Exclusive Tax),Сумма счета (без учета налога), Average Commission Rate,Средний уровень комиссии, Sales Payment Summary,Сводка по продажам, @@ -8579,7 +8579,7 @@ Student Fee Collection,Сбор студенческой платы, Student Monthly Attendance Sheet,Ежемесячная посещаемость студентов, Subcontracted Item To Be Received,"Субподрядный предмет, подлежащий получению", Subcontracted Raw Materials To Be Transferred,Субподрядное сырье для передачи, -Supplier Ledger Summary,Список поставщиков, +Supplier Ledger Summary,Сводка книги поставщиков, Supplier-Wise Sales Analytics,Аналитика продаж в разрезе поставщиков, Support Hour Distribution,Распределение поддержки, TDS Computation Summary,Сводка расчетов TDS, From a4c2eccd56530a6566937ce888c99b8ac883c411 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Tue, 31 May 2022 13:48:32 +0300 Subject: [PATCH 37/98] Update ru.csv --- erpnext/translations/ru.csv | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 3e169fbde7..1e2877473f 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -3199,7 +3199,7 @@ Used Leaves,Используемые листы, User,Пользователь, User ID,ID пользователя, User ID not set for Employee {0},ID пользователя не установлен для сотрудника {0}, -User Remark,Примечание Пользователь, +User Remark,Примечание пользователя, User has not applied rule on the invoice {0},Пользователь не применил правило к счету {0}, User {0} already exists,Пользователь {0} уже существует, User {0} created,Пользователь {0} создан, @@ -3871,11 +3871,15 @@ Not allowed to create accounting dimension for {0},Не разрешено со Not permitted. Please disable the Lab Test Template,"Не разрешено Пожалуйста, отключите шаблон лабораторного теста", Note,Заметка, Notes: ,Заметки: , -On Converting Opportunity,О возможности конвертации, -On Purchase Order Submission,При подаче заказа на поставку, -On Sales Order Submission,На подаче заказа клиента, -On Task Completion,По завершении задачи, +On Converting Opportunity,Конвертацию возможности, +On Purchase Order Submission,Офомление заказа на закупку, +On Sales Order Submission,Оформление заказа на продажу, +On Task Completion,Завершении задачи, On {0} Creation,На {0} создании, +On Item Creation,Создание продукта, +On Lead Creation,Создание лида, +On Supplier Creation,Создание поставщика, +On Customer Creation,Создание клиента, Only .csv and .xlsx files are supported currently,В настоящее время поддерживаются только файлы .csv и .xlsx, Only expired allocation can be cancelled,Только истекшее распределение может быть отменено, Only users with the {0} role can create backdated leave applications,Только пользователи с ролью {0} могут создавать оставленные приложения с задним сроком действия, From e523fd6bad42ff8caf36969b7cf86d8fff8430e3 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Tue, 31 May 2022 17:04:24 +0300 Subject: [PATCH 38/98] Update ru.csv fix logic --- erpnext/translations/ru.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 1e2877473f..0e0795bcb8 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -7524,7 +7524,7 @@ Expected Time (in hours),Ожидаемое время (в часах), Is Milestone,Является этапом, Task Description,Описание задания, Dependencies,Зависимости, -Dependent Tasks,Зависимые задачи, +Dependent Tasks,Зависит от задач, Depends on Tasks,Зависит от задач, Actual Start Date (via Time Sheet),Фактическая дата начала (по табелю учета рабочего времени), Actual Time (in hours),Фактическое время (в часах), From 37d3c5121fca8aa4f11da12e5875efbc089e4df2 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Mon, 6 Jun 2022 11:23:44 +0300 Subject: [PATCH 39/98] Update ru.csv --- erpnext/translations/ru.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 0e0795bcb8..a880f9c7be 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -6445,7 +6445,7 @@ Job Applicant,Соискатель работы, Applicant Name,Имя заявителя, Appointment Date,Назначенная дата, Appointment Letter Template,Шаблон письма о назначении, -Body,Тело, +Body,Содержимое, Closing Notes,Заметки, Appointment Letter content,Письмо о назначении, Appraisal,Оценка, From 6f4b91d94f7824a1e85e0c0810d9921a8e8b8a6c Mon Sep 17 00:00:00 2001 From: Vladislav Date: Mon, 6 Jun 2022 14:41:54 +0300 Subject: [PATCH 40/98] Update ru.csv --- erpnext/translations/ru.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index a880f9c7be..590d60f2ab 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -1397,7 +1397,7 @@ Job Card,Карточка работы, Job Description,Описание работы, Job Offer,Предложение работы, Job card {0} created,Карта работы {0} создана, -Jobs,Работы, +Jobs,Вакансии, Join,Присоединиться, Journal Entries {0} are un-linked,Записи в журнале {0} не-связаны, Journal Entry,Запись в журнале, From be50d7dd1b2fff127ca5c6c1f9b4b1bf4992bf67 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Thu, 9 Jun 2022 14:11:12 +0300 Subject: [PATCH 41/98] Update ru.csv --- erpnext/translations/ru.csv | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 590d60f2ab..743b29493c 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -288,7 +288,7 @@ Asset {0} must be submitted,Актив {0} должен быть проведе Assets,Активы, Assign,Назначить, Assign Salary Structure,Назначить структуру заработной платы, -Assign To,Назначить в, +Assign To,Назначить для, Assign to Employees,Назначить сотрудникам, Assigning Structures...,Назначение структур..., Associate,Помощник, @@ -490,7 +490,7 @@ Capital Equipments,Капитальные оборудование, Capital Stock,Капитал, Capital Work in Progress,Капитальная работа в процессе, Cart,Корзина, -Cart is Empty,Корзина Пусто, +Cart is Empty,Корзина пуста, Case No(s) already in use. Try from Case No {0},Случай Нет (ы) уже используется. Попробуйте из дела № {0}, Cash,Наличные, Cash Flow Statement,О движении денежных средств, @@ -578,7 +578,7 @@ Compensatory Off,Компенсационные Выкл, Compensatory leave request days not in valid holidays,Дни запроса на получение компенсационных отчислений не действительны, Complaint,Жалоба, Completion Date,Дата завершения, -Computer,компьютер, +Computer,Компьютер, Condition,Условия, Configure,Конфигурировать, Configure {0},Настроить {0}, @@ -643,7 +643,6 @@ Course Code: ,Код курса: , Course Enrollment {0} does not exists,Зачисление на курс {0} не существует, Course Schedule,Расписание курса, Course: ,Курс: , -Cr,Cr, Create,Создать, Create BOM,Создать спецификацию, Create Delivery Trip,Создать маршрут доставки, @@ -795,7 +794,6 @@ Defense,Оборона, Define Project type.,Установите тип проекта., Define budget for a financial year.,Определить бюджет на финансовый год., Define various loan types,Определение различных видов кредита, -Del,Del, Delay in payment (Days),Задержка в оплате (дни), Delete all the Transactions for this Company,Удалить все транзакции этой компании, Deletion is not permitted for country {0},Для страны не разрешено удаление {0}, @@ -4949,14 +4947,14 @@ Max Qty,Макс. кол-во, Min Amt,Мин Amt, Max Amt,Макс Амт, Period Settings,Настройки периода, -Margin,Разница, +Margin,Маржа, Margin Type,Тип маржа, Margin Rate or Amount,Маржинальная ставка или сумма, Price Discount Scheme,Схема скидок, Rate or Discount,Стоимость или скидка, Discount Percentage,Скидка в процентах, Discount Amount,Сумма скидки, -For Price List,Для Прейскурантом, +For Price List,Для прайс-листа, Product Discount Scheme,Схема скидок на товары, Same Item,Тот же пункт, Free Item,Бесплатный товар, From 3e66f4214dff34a96762d8e4ccdec675e02f12a4 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Fri, 10 Jun 2022 13:12:09 +0300 Subject: [PATCH 42/98] Update ru.csv --- erpnext/translations/ru.csv | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 743b29493c..6c89f4908b 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -3091,7 +3091,7 @@ Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total, Total Payments,Всего платежей, Total Present,Итого Текущая, Total Qty,Общее количество, -Total Quantity,Общая численность, +Total Quantity,Общее количество, Total Revenue,Общий доход, Total Student,Всего учеников, Total Target,Всего целей, @@ -3498,7 +3498,7 @@ Postal,Почтовый, Postal Code,Почтовый индекс, Previous,Предыдущая, Provider,Поставщик, -Read Only,Только чтения, +Read Only,Только чтение, Recipient,Сторона-реципиент, Reviews,Отзывы, Sender,Отправитель, @@ -3879,7 +3879,8 @@ On Lead Creation,Создание лида, On Supplier Creation,Создание поставщика, On Customer Creation,Создание клиента, Only .csv and .xlsx files are supported currently,В настоящее время поддерживаются только файлы .csv и .xlsx, -Only expired allocation can be cancelled,Только истекшее распределение может быть отменено, +Only expired allocation can be cancelled, +истекшее распределение может быть отменено, Only users with the {0} role can create backdated leave applications,Только пользователи с ролью {0} могут создавать оставленные приложения с задним сроком действия, Open,Открыт, Open Contact,Открытый контакт, @@ -4059,7 +4060,7 @@ Show Stock Ageing Data,Показать данные о старении зап Show Warehouse-wise Stock,Показать складской запас, Size,Размер, Something went wrong while evaluating the quiz.,Что-то пошло не так при оценке теста., -Sr,Sr, +Sr,№, Start,Начать, Start Date cannot be before the current date,Дата начала не может быть раньше текущей даты, Start Time,Время начала, From 84620a775d8b0e52fcaea644f867768faa37ad85 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Fri, 10 Jun 2022 13:35:51 +0300 Subject: [PATCH 43/98] Update ru.csv --- erpnext/translations/ru.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 6c89f4908b..a2f23cd76e 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -995,7 +995,7 @@ Expenses,Расходы, Expenses Included In Asset Valuation,"Расходы, включенные в оценку активов", Expenses Included In Valuation,"Затрат, включаемых в оценке", Expired Batches,Просроченные партии, -Expires On,Годен до, +Expires On,Актуален до, Expiring On,Срок действия, Expiry (In Days),Срок действия (в днях), Explore,Обзор, From 89e720fdf48cd5664c8bbdbfc47f6e54be31c3a0 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Fri, 10 Jun 2022 17:24:28 +0300 Subject: [PATCH 44/98] Update ru.csv --- erpnext/translations/ru.csv | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index a2f23cd76e..b348859835 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -44,7 +44,7 @@ Accessable Value,Доступная стоимость, Account,Аккаунт, Account Number,Номер аккаунта, Account Number {0} already used in account {1},"Номер счета {0}, уже использованный в учетной записи {1}", -Account Pay Only,Счет Оплатить только, +Account Pay Only,Только оплатить счет, Account Type,Тип учетной записи, Account Type for {0} must be {1},Тип счета для {0} должен быть {1}, "Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'","Баланс счета в Кредите, запрещена установка 'Баланс должен быть' как 'Дебет'", @@ -692,7 +692,7 @@ Created {0} scorecards for {1} between: ,Созданы {0} оценочные Creating Company and Importing Chart of Accounts,Создание компании и импорт плана счетов, Creating Fees,Создание сборов, Creating Payment Entries......,Создание платежных записей......, -Creating Salary Slips...,Создание зарплатных листков..., +Creating Salary Slips...,Создание зарплатных ведомостей..., Creating student groups,Создание групп студентов, Creating {0} Invoice,Создание {0} счета, Credit,Кредит, @@ -2874,7 +2874,7 @@ Supplier Id,Id поставщика, Supplier Invoice Date cannot be greater than Posting Date,"Дата Поставщик Счет не может быть больше, чем Дата публикации", Supplier Invoice No,Поставщик Счет №, Supplier Invoice No exists in Purchase Invoice {0},Номер счета поставщика отсутствует в счете на покупку {0}, -Supplier Name,наименование поставщика, +Supplier Name,Наименование поставщика, Supplier Part No,Деталь поставщика №, Supplier Quotation,Предложение поставщика, Supplier Scorecard,Оценочная карта поставщика, @@ -4047,7 +4047,7 @@ Server Error,Ошибка сервера, Service Level Agreement has been changed to {0}.,Соглашение об уровне обслуживания изменено на {0}., Service Level Agreement was reset.,Соглашение об уровне обслуживания было сброшено., Service Level Agreement with Entity Type {0} and Entity {1} already exists.,Соглашение об уровне обслуживания с типом объекта {0} и объектом {1} уже существует., -Set,Задать, +Set,Комплект, Set Meta Tags,Установить метатеги, Set {0} in company {1},Установить {0} в компании {1}, Setup,Настройки, @@ -5085,8 +5085,8 @@ Allow Zero Valuation Rate,Разрешить нулевую оценку, Item Tax Rate,Ставка налогов на продукт, Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges,Налоговый Подробная таблица выбирается из мастера элемента в виде строки и хранится в этой области.\n Используется по налогам и сборам, Purchase Order Item,Заказ товара, -Purchase Receipt Detail,Деталь квитанции о покупке, -Item Weight Details,Деталь Вес Подробности, +Purchase Receipt Detail,Сведения о квитанции о покупке, +Item Weight Details,Сведения о весе товара, Weight Per Unit,Вес на единицу, Total Weight,Общий вес, Weight UOM,Вес Единица измерения, @@ -5237,7 +5237,7 @@ Billing Interval,Интервал выставления счетов, Billing Interval Count,Счет интервала фактурирования, "Number of intervals for the interval field e.g if Interval is 'Days' and Billing Interval Count is 3, invoices will be generated every 3 days","Количество интервалов для поля интервалов, например, если Interval является «Days», а количество интервалов фактурирования - 3, счета-фактуры будут генерироваться каждые 3 дня", Payment Plan,Платежный план, -Subscription Plan Detail,Деталь плана подписки, +Subscription Plan Detail,Сведения о плана подписки, Plan,План, Subscription Settings,Настройки подписки, Grace Period,Льготный период, @@ -6197,7 +6197,7 @@ Inpatient Occupancy,Стационарное размещение, Occupancy Status,Статус занятости, Vacant,Вакантно, Occupied,Занято, -Item Details,Детальная информация о товаре, +Item Details,Детальная информация о продукте, UOM Conversion in Hours,Преобразование UOM в часы, Rate / UOM,Скорость / UOM, Change in Item,Изменение продукта, @@ -6869,7 +6869,7 @@ Only Tax Impact (Cannot Claim But Part of Taxable Income),Только нало Create Separate Payment Entry Against Benefit Claim,Создать отдельную заявку на подачу заявки на получение пособия, Condition and Formula,Состояние и формула, Amount based on formula,Сумма на основе формулы, -Formula,формула, +Formula,Формула, Salary Detail,Заработная плата: Подробности, Component,Компонент, Do not include in total,Не включать в общей сложности, @@ -7186,7 +7186,7 @@ Ordered Quantity,Заказанное количество, Item to be manufactured or repacked,Продукт должен быть произведен или переупакован, Quantity of item obtained after manufacturing / repacking from given quantities of raw materials,Количество пункта получены после изготовления / переупаковка от заданных величин сырья, Set rate of sub-assembly item based on BOM,Установить скорость сборки на основе спецификации, -Allow Alternative Item,Разрешить альтернативный элемент, +Allow Alternative Item,Разрешить альтернативный продукт, Item UOM,Единиц продукта, Conversion Rate,Коэффициент конверсии, Rate Of Materials Based On,Оценить материалов на основе, @@ -8193,7 +8193,7 @@ Packed Item,Упаковано, To Warehouse (Optional),На склад (Необязательно), Actual Batch Quantity,Фактическое количество партий, Prevdoc DocType,Prevdoc DocType, -Parent Detail docname,Родитель Деталь DOCNAME, +Parent Detail docname,Сведения о родителе docname, "Generate packing slips for packages to be delivered. Used to notify package number, package contents and its weight.","Создаёт упаковочные листы к упаковкам для доставки. Содержит номер упаковки, перечень содержимого и вес.", Indicates that the package is a part of this delivery (Only Draft),"Указывает, что пакет является частью этой поставки (только проект)", MAT-PAC-.YYYY.-,MAT-PAC-.YYYY.-, From 7bdeac49f4016dca5ba70b6c78c2584142dc01fd Mon Sep 17 00:00:00 2001 From: Vladislav Date: Sat, 11 Jun 2022 15:59:49 +0300 Subject: [PATCH 45/98] Update ru.csv --- erpnext/translations/ru.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index b348859835..3a9b5ba703 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -4514,7 +4514,7 @@ Mandatory For Profit and Loss Account,Обязательно для счета Accounting Period,Период учета, Period Name,Название периода, Closed Documents,Закрытые документы, -Accounts Settings,Настройки аккаунта, +Accounts Settings,Настройка счетов, Settings for Accounts,Настройки для счетов, Make Accounting Entry For Every Stock Movement,Создавать бухгалтерские проводки при каждом перемещении запасов, Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,"Пользователи с этой ролью могут замороживать счета, а также создавать / изменять бухгалтерские проводки замороженных счетов", From ca9d55a2fdc8828863f412d99f40225a8c1f6f72 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 14 Jun 2022 13:11:46 +0530 Subject: [PATCH 46/98] chore: update err msg for FG Item in PO --- .../buying/doctype/purchase_order/purchase_order.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index e0cc43ff67..dd10c93b1d 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -196,21 +196,21 @@ class PurchaseOrder(BuyingController): for item in self.items: if not item.fg_item: frappe.throw( - _("Finished Good Item is not specified for service item {0} at row {1}").format( - item.item_code, item.idx + _("Row #{0}: Finished Good Item is not specified for service item {1}").format( + item.idx, item.item_code ) ) else: if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"): frappe.throw( _( - "Finished Good Item {0} must be a sub-contracted item for service item {1} at row {2}" - ).format(item.fg_item, item.item_code, item.idx) + "Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2}" + ).format(item.idx, item.fg_item, item.item_code) ) if not item.fg_item_qty: frappe.throw( - _("Finished Good Item Qty is not specified for service item {0} at row {1}").format( - item.item_code, item.idx + _("Row #{0}: Finished Good Item Qty is not specified for service item {0}").format( + item.idx, item.item_code ) ) From c869077c92f7dac5f166283d770e6d97d7cd9fe6 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Thu, 16 Jun 2022 18:18:35 +0300 Subject: [PATCH 47/98] Update ru.csv --- erpnext/translations/ru.csv | 44 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 3a9b5ba703..38c3828fb7 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -5803,7 +5803,7 @@ Make Academic Term Mandatory,Сделать академический срок Skip User creation for new Student,Пропустить создание пользователя для нового студента, "By default, a new User is created for every new Student. If enabled, no new User will be created when a new Student is created.","По умолчанию для каждого нового Студента создается новый Пользователь. Если этот параметр включен, при создании нового Студента новый Пользователь не создается.", Instructor Records to be created by,Записи инструкторов должны быть созданы, -Employee Number,Общее число сотрудников, +Employee Number,Номер сотрудника, Fee Category,Категория платы, Fee Component,Компонент платы, Fees Category,Категория плат, @@ -6870,7 +6870,7 @@ Create Separate Payment Entry Against Benefit Claim,Создать отдель Condition and Formula,Состояние и формула, Amount based on formula,Сумма на основе формулы, Formula,Формула, -Salary Detail,Заработная плата: Подробности, +Salary Detail,Подробно об заработной плате, Component,Компонент, Do not include in total,Не включать в общей сложности, Default Amount,По умолчанию количество, @@ -6892,7 +6892,7 @@ Total Principal Amount,Общая сумма, Total Interest Amount,Общая сумма процентов, Total Loan Repayment,Общая сумма погашения кредита, net pay info,Чистая информация платить, -Gross Pay - Total Deduction - Loan Repayment,Gross Pay - Итого Вычет - Погашение кредита, +Gross Pay - Total Deduction - Loan Repayment,Валовая заработная плата - Общий вычет - Погашение кредита, Total in words,Всего в словах, Net Pay (in words) will be visible once you save the Salary Slip.,"Чистая плата (прописью) будет видна, как только вы сохраните зарплатную ведомость.", Salary Component for timesheet based payroll.,Компонент заработной платы для расчета зарплаты на основе расписания., @@ -6962,7 +6962,7 @@ Trainer Email,Электронная почта тренера, Attendees,Присутствующие, Employee Emails,Электронные почты сотрудников, Training Event Employee,Обучение сотрудников Событие, -Invited,приглашенный, +Invited,Приглашенный, Feedback Submitted,Отзыв отправлен, Optional,Необязательный, Training Result Employee,Результат обучения сотрудника, @@ -7601,7 +7601,7 @@ Invoices with no Place Of Supply,Счета без места поставки, Import Supplier Invoice,Импортная накладная поставщика, Invoice Series,Серия счетов, Upload XML Invoices,Загрузить XML-счета, -Zip File,Zip-файл, +Zip File,Zip файл, Import Invoices,Импорт счетов, Click on Import Invoices button once the zip file has been attached to the document. Any errors related to processing will be shown in the Error Log.,"Нажмите кнопку «Импортировать счета-фактуры», когда файл zip прикреплен к документу. Любые ошибки, связанные с обработкой, будут отображаться в журнале ошибок.", Lower Deduction Certificate,Свидетельство о нижнем удержании, @@ -7636,7 +7636,7 @@ Restaurant Order Entry Item,Номер заказа заказа рестора Served,Подается, Restaurant Reservation,Бронирование ресторанов, Waitlisted,Лист ожидания, -No Show,Нет шоу, +No Show,Не показывать, No of People,Нет людей, Reservation Time,Время резервирования, Reservation End Time,Время окончания бронирования, @@ -7874,8 +7874,8 @@ Disable In Words,Отключить в словах, "If disable, 'In Words' field will not be visible in any transaction","Если отключить, "В словах" поле не будет видно в любой сделке", Item Classification,Продуктовая классификация, General Settings,Основные настройки, -Item Group Name,Пункт Название группы, -Parent Item Group,Родитель Пункт Группа, +Item Group Name,Название группы продуктов, +Parent Item Group,Родительская группа продукта, Item Group Defaults,Элемент группы по умолчанию, Item Tax,Налог на продукт, Check this if you want to show in website,"Проверьте это, если вы хотите показать в веб-сайт", @@ -7972,13 +7972,13 @@ Customs Tariff Number,Номер таможенного тарифа, Tariff Number,Тарифный номер, Delivery To,Доставка, MAT-DN-.YYYY.-,MAT-DN-.YYYY.-, -Is Return,Является Вернуться, +Is Return,Возврат, Issue Credit Note,Кредитная кредитная карта, -Return Against Delivery Note,Вернуться На накладной, -Customer's Purchase Order No,Клиентам Заказ Нет, +Return Against Delivery Note,Возврат по накладной, +Customer's Purchase Order No,Заказ клиента №, Billing Address Name,Название адреса для выставления счета, Required only for sample item.,Требуется только для образца пункта., -"If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Если вы создали стандартный шаблон в шаблонах Налоги с налогами и сбором платежей, выберите его и нажмите кнопку ниже.", +"If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Если вы создали стандартный шаблон в Шаблоне налогов и сборов с продаж, выберите его и нажмите кнопку ниже.", In Words will be visible once you save the Delivery Note.,По словам будет виден только вы сохраните накладной., In Words (Export) will be visible once you save the Delivery Note.,В Слов (Экспорт) будут видны только вы сохраните накладной., Transporter Info,Информация для транспортировки, @@ -7992,8 +7992,8 @@ Installation Status,Состояние установки, Excise Page Number,Количество Акцизный Страница, Instructions,Инструкции, From Warehouse,Со склада, -Against Sales Order,По Сделке, -Against Sales Order Item,По Продукту Сделки, +Against Sales Order,По сделке, +Against Sales Order Item,По позиции сделки, Against Sales Invoice,Повторная накладная, Against Sales Invoice Item,Счет на продажу продукта, Available Batch Qty at From Warehouse,Доступные Пакетная Кол-во на со склада, @@ -8009,7 +8009,7 @@ Delivery Stop,Остановить доставку, Lock,Заблокировано, Visited,Посещен, Order Information,запросить информацию, -Contact Information,Контакты, +Contact Information,Контактная информация, Email sent to,Письмо отправлено, Dispatch Information,Информация о доставке, Estimated Arrival,Ожидаемое прибытие, @@ -8122,7 +8122,7 @@ Two-way,Двусторонний, Alternative Item Name,Альтернативное название продукта, Attribute Name,Название атрибута, Numeric Values,Числовые значения, -From Range,От хребта, +From Range,Из диапазона, Increment,Приращение, To Range,В диапазоне, Item Attribute Values,Пункт значений атрибутов, @@ -8144,7 +8144,7 @@ Default Supplier,Поставщик по умолчанию, Default Expense Account,Счет учета затрат по умолчанию, Sales Defaults,По умолчанию, Default Selling Cost Center,По умолчанию Продажа Стоимость центр, -Item Manufacturer,Пункт Производитель, +Item Manufacturer,Производитель товара, Item Price,Цена продукта, Packing Unit,Упаковочный блок, Quantity that must be bought or sold per UOM,"Количество, которое необходимо купить или продать за UOM", @@ -8178,7 +8178,7 @@ Purchase Receipts,Покупка Поступления, Purchase Receipt Items,Покупка продуктов, Get Items From Purchase Receipts,Получить продукты из покупки., Distribute Charges Based On,Распределите платежи на основе, -Landed Cost Help,Земельные Стоимость Помощь, +Landed Cost Help,Справка по стоимости доставки, Manufacturers used in Items,Производители использовали в пунктах, Limited to 12 characters,Ограничено до 12 символов, MAT-MR-.YYYY.-,МАТ-MR-.YYYY.-, @@ -8187,8 +8187,8 @@ Transferred,Переданы, % Ordered,% заказано, Terms and Conditions Content,Условия Содержимое, Quantity and Warehouse,Количество и Склад, -Lead Time Date,Время и Дата Лида, -Min Order Qty,Минимальный заказ Кол-во, +Lead Time Date,Дата выполнения заказа, +Min Order Qty,Минимальное количество для заказа, Packed Item,Упаковано, To Warehouse (Optional),На склад (Необязательно), Actual Batch Quantity,Фактическое количество партий, @@ -8354,7 +8354,7 @@ Automatically Set Serial Nos based on FIFO,Автоматически устан Auto Material Request,Автоматический запрос материалов, Inter Warehouse Transfer Settings,Настройки передачи между складами, Freeze Stock Entries,Замораживание поступления запасов, -Stock Frozen Upto,остатки заморожены до, +Stock Frozen Upto,Остатки заморожены до, Batch Identification,Идентификация партии, Use Naming Series,Использовать серийный номер, Naming Series Prefix,Префикс Идентификации по Имени, @@ -8373,7 +8373,7 @@ Issue Split From,Выпуск Сплит От, Service Level,Уровень обслуживания, Response By,Ответ от, Response By Variance,Ответ по отклонениям, -Ongoing,постоянный, +Ongoing,Постоянный, Resolution By,Разрешение по, Resolution By Variance,Разрешение по отклонениям, Service Level Agreement Creation,Создание соглашения об уровне обслуживания, From 75e1f01b9d6359e090e1d8ffb9c7cec56224023e Mon Sep 17 00:00:00 2001 From: Vladislav Date: Thu, 16 Jun 2022 18:32:57 +0300 Subject: [PATCH 48/98] Update ru.csv --- erpnext/translations/ru.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 38c3828fb7..2f253ec53b 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -117,7 +117,7 @@ Add Item,Добавить продукт, Add Items,Добавить продукты, Add Leads,Добавить лид, Add Multiple Tasks,Добавить несколько задач, -Add Row,Добавить ряд, +Add Row,Добавить строку, Add Sales Partners,Добавить партнеров по продажам, Add Serial No,Добавить серийный номер, Add Students,Добавить студентов, From 0e8aad90380f00a0e2eafc1315d865fff249f8f8 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Fri, 17 Jun 2022 00:28:48 +0300 Subject: [PATCH 49/98] Update ru.csv --- erpnext/translations/ru.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 2f253ec53b..7429f4a531 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -1411,7 +1411,7 @@ Lab Test UOM,Лабораторная проверка UOM, Lab Tests and Vital Signs,Лабораторные тесты и жизненные знаки, Lab result datetime cannot be before testing datetime,Лабораторный результат datetime не может быть до тестирования даты и времени, Lab testing datetime cannot be before collection datetime,Лабораторное тестирование datetime не может быть до даты сбора данных, -Label,Ярлык, +Label,Метка, Laboratory,Лаборатория, Language Name,Название языка, Large,Большой, @@ -5199,7 +5199,7 @@ Address and Contacts,Адрес и контакты, Contact List,Список контактов, Hidden list maintaining the list of contacts linked to Shareholder,"Скрытый список, поддерживающий список контактов, связанных с Акционером", Specify conditions to calculate shipping amount,Укажите условия для расчета суммы доставки, -Shipping Rule Label,Название правила доставки, +Shipping Rule Label,Метка правила доставки, example: Next Day Shipping,Пример: доставка на следующий день, Shipping Rule Type,Тип правила доставки, Shipping Account,Счет доставки, From 5002f1f1e5e4823730d84c30c991c8ecc1de76ac Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 17 Jun 2022 15:43:59 +0530 Subject: [PATCH 50/98] feat: Add hidden field "is_old_subcontracting_flow" in PO, PR and PI --- .../purchase_invoice/purchase_invoice.json | 15 ++++++++++++--- .../doctype/purchase_order/purchase_order.json | 13 +++++++++++-- erpnext/patches.txt | 1 + ...tracted_value_to_is_old_subcontracting_flow.py | 12 ++++++++++++ .../purchase_receipt/purchase_receipt.json | 13 +++++++++++-- 5 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 erpnext/patches/v14_0/copy_is_subcontracted_value_to_is_old_subcontracting_flow.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 181dcc34de..1c5c0609f1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -169,7 +169,8 @@ "column_break_114", "auto_repeat", "update_auto_repeat_reference", - "per_received" + "per_received", + "is_old_subcontracting_flow" ], "fields": [ { @@ -1416,13 +1417,21 @@ "label": "Advance Tax", "options": "Advance Tax", "read_only": 1 - } + }, + { + "default": "0", + "fieldname": "is_old_subcontracting_flow", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Old Subcontracting Flow", + "read_only": 1 + } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-11-25 13:31:02.716727", + "modified": "2022-06-15 15:40:58.527065", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 07320d0a0a..b622b4f1be 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -141,7 +141,8 @@ "party_account_currency", "is_internal_supplier", "represents_company", - "inter_company_order_reference" + "inter_company_order_reference", + "is_old_subcontracting_flow" ], "fields": [ { @@ -1161,13 +1162,21 @@ "fieldtype": "Link", "label": "Project", "options": "Project" + }, + { + "default": "0", + "fieldname": "is_old_subcontracting_flow", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Old Subcontracting Flow", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-04-26 18:46:58.863174", + "modified": "2022-06-15 15:40:58.527065", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 5a984635fd..51038a5b39 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -374,3 +374,4 @@ erpnext.patches.v13_0.set_per_billed_in_return_delivery_note execute:frappe.delete_doc("DocType", "Naming Series") erpnext.patches.v13_0.set_payroll_entry_status erpnext.patches.v13_0.job_card_status_on_hold +erpnext.patches.v14_0.copy_is_subcontracted_value_to_is_old_subcontracting_flow diff --git a/erpnext/patches/v14_0/copy_is_subcontracted_value_to_is_old_subcontracting_flow.py b/erpnext/patches/v14_0/copy_is_subcontracted_value_to_is_old_subcontracting_flow.py new file mode 100644 index 0000000000..607ef69538 --- /dev/null +++ b/erpnext/patches/v14_0/copy_is_subcontracted_value_to_is_old_subcontracting_flow.py @@ -0,0 +1,12 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe + + +def execute(): + for doctype in ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]: + tab = frappe.qb.DocType(doctype).as_("tab") + frappe.qb.update(tab).set(tab.is_old_subcontracting_flow, 1).where( + tab.is_subcontracted == 1 + ).run() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 923ceb36cd..b3d38858d0 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -133,7 +133,8 @@ "transporter_name", "column_break5", "lr_no", - "lr_date" + "lr_date", + "is_old_subcontracting_flow" ], "fields": [ { @@ -1142,13 +1143,21 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "is_old_subcontracting_flow", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Old Subcontracting Flow", + "read_only": 1 } ], "icon": "fa fa-truck", "idx": 261, "is_submittable": 1, "links": [], - "modified": "2022-05-27 15:59:18.550583", + "modified": "2022-06-15 15:43:40.664382", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From ca24b5287e8d8b4d4da6bc6661aaccc94997a9f2 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 17 Jun 2022 16:25:18 +0530 Subject: [PATCH 51/98] chore: make "is_subcontracted" field read-only in PR and PI --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 3 ++- erpnext/stock/doctype/purchase_receipt/purchase_receipt.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 1c5c0609f1..534b879e78 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -548,7 +548,8 @@ "fieldname": "is_subcontracted", "fieldtype": "Check", "label": "Is Subcontracted", - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "fieldname": "items_section", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index b3d38858d0..a70415dfc3 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -443,7 +443,8 @@ "label": "Is Subcontracted", "oldfieldname": "is_subcontracted", "oldfieldtype": "Select", - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "depends_on": "eval:doc.is_subcontracted", From b5e22c8e16e64f7ee06b2edeece5fa902b168ac6 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Sun, 19 Jun 2022 21:54:06 +0300 Subject: [PATCH 52/98] Update ru.csv --- erpnext/translations/ru.csv | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 7429f4a531..a4bfb86c01 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -3879,8 +3879,7 @@ On Lead Creation,Создание лида, On Supplier Creation,Создание поставщика, On Customer Creation,Создание клиента, Only .csv and .xlsx files are supported currently,В настоящее время поддерживаются только файлы .csv и .xlsx, -Only expired allocation can be cancelled, -истекшее распределение может быть отменено, +Only expired allocation can be cancelled,Отменить можно только просроченное распределение, Only users with the {0} role can create backdated leave applications,Только пользователи с ролью {0} могут создавать оставленные приложения с задним сроком действия, Open,Открыт, Open Contact,Открытый контакт, From 6d89b2fa28f4f027c9eb6b3189d73fe3904f65f4 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 18 Jun 2022 15:46:59 +0530 Subject: [PATCH 53/98] refactor: backport old subcontracting code --- .../purchase_invoice/purchase_invoice.js | 4 + .../purchase_invoice/purchase_invoice.py | 7 + .../purchase_invoice_item.json | 9 +- .../doctype/purchase_order/purchase_order.js | 225 +++++++++- .../purchase_order/purchase_order.json | 4 - .../doctype/purchase_order/purchase_order.py | 43 +- .../purchase_order_dashboard.py | 2 +- .../purchase_order_item.json | 16 +- .../subcontract_order_summary.js | 20 +- .../subcontract_order_summary.py | 68 +-- .../subcontracted_item_to_be_received.js | 7 + .../subcontracted_item_to_be_received.json | 2 +- .../subcontracted_item_to_be_received.py | 36 +- .../test_subcontracted_item_to_be_received.py | 3 +- ...tracted_raw_materials_to_be_transferred.js | 7 + ...acted_raw_materials_to_be_transferred.json | 2 +- ...tracted_raw_materials_to_be_transferred.py | 51 ++- ...tracted_raw_materials_to_be_transferred.py | 9 +- erpnext/controllers/accounts_controller.py | 4 + erpnext/controllers/buying_controller.py | 50 ++- .../controllers/subcontracting_controller.py | 404 ++++++++++++++---- .../tests/test_subcontracting_controller.py | 6 +- .../v13_0/add_bin_unique_constraint.py | 4 + erpnext/public/js/controllers/buying.js | 10 +- erpnext/public/js/controllers/transaction.js | 3 +- erpnext/public/js/utils.js | 6 +- erpnext/stock/doctype/bin/bin.py | 75 ++-- .../item_alternative/test_item_alternative.py | 2 +- .../purchase_receipt/purchase_receipt.js | 4 + .../purchase_receipt/purchase_receipt.py | 3 + .../purchase_receipt_item.json | 4 +- .../stock/doctype/stock_entry/stock_entry.js | 27 +- .../doctype/stock_entry/stock_entry.json | 4 +- .../stock/doctype/stock_entry/stock_entry.py | 186 +++++--- erpnext/stock/get_item_details.py | 9 +- erpnext/stock/stock_ledger.py | 7 + .../subcontracting_order.js | 18 +- .../subcontracting_order.py | 142 +----- .../test_subcontracting_order.py | 2 +- .../subcontracting_receipt.py | 17 +- .../test_subcontracting_receipt.py | 4 +- 41 files changed, 1047 insertions(+), 459 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 172ac9e009..306d01dc1f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -572,6 +572,10 @@ frappe.ui.form.on("Purchase Invoice", { }, is_subcontracted: function(frm) { + if (frm.doc.is_old_subcontracting_flow) { + erpnext.buying.get_default_bom(frm); + } + frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted); }, diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 5c8ab64300..6281877756 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -513,6 +513,10 @@ class PurchaseInvoice(BuyingController): # because updating ordered qty in bin depends upon updated ordered qty in PO if self.update_stock == 1: self.update_stock_ledger() + + if self.is_old_subcontracting_flow: + self.set_consumed_qty_in_subcontract_order() + from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit update_serial_nos_after_submit(self, "items") @@ -1416,6 +1420,9 @@ class PurchaseInvoice(BuyingController): self.update_stock_ledger() self.delete_auto_created_batches() + if self.is_old_subcontracting_flow: + self.set_consumed_qty_in_subcontract_order() + self.make_gl_entries_on_cancel() if self.update_stock == 1: diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index dd62886d96..387b2cb354 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -616,11 +616,13 @@ "search_index": 1 }, { + "depends_on": "eval:parent.is_old_subcontracting_flow", "fieldname": "bom", "fieldtype": "Link", "label": "BOM", "options": "BOM", - "read_only": 1 + "read_only": 1, + "read_only_depends_on": "eval:!parent.is_old_subcontracting_flow" }, { "default": "0", @@ -872,7 +874,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-11-15 17:04:07.191013", + "modified": "2022-06-15 16:02:15.196835", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", @@ -880,5 +882,6 @@ "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 6aba373bc6..c635a7fa71 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -7,6 +7,19 @@ frappe.provide("erpnext.accounts.dimensions"); frappe.ui.form.on("Purchase Order", { setup: function(frm) { + + if (frm.doc.is_old_subcontracting_flow) { + frm.set_query("reserve_warehouse", "supplied_items", function() { + return { + filters: { + "company": frm.doc.company, + "name": ['!=', frm.doc.supplier_warehouse], + "is_group": 0 + } + } + }); + } + frm.set_indicator_formatter('item_code', function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" }) @@ -19,7 +32,10 @@ frappe.ui.form.on("Purchase Order", { frm.set_query("fg_item", "items", function() { return { - filters: {'is_sub_contracted_item': 1} + filters: { + 'is_sub_contracted_item': 1, + 'default_bom': ['!=', ''] + } } }); }, @@ -28,6 +44,44 @@ frappe.ui.form.on("Purchase Order", { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); }, + refresh: function(frm) { + if(frm.doc.is_old_subcontracting_flow) + frm.trigger('get_materials_from_supplier'); + }, + + get_materials_from_supplier: function(frm) { + let po_details = []; + + if (frm.doc.supplied_items && (frm.doc.per_received == 100 || frm.doc.status === 'Closed')) { + frm.doc.supplied_items.forEach(d => { + if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) { + po_details.push(d.name) + } + }); + } + + if (po_details && po_details.length) { + frm.add_custom_button(__('Return of Components'), () => { + frm.call({ + method: 'erpnext.controllers.subcontracting_controller.get_materials_from_supplier', + freeze: true, + freeze_message: __('Creating Stock Entry'), + args: { + subcontract_order: frm.doc.name, + rm_details: po_details, + order_doctype: cur_frm.doc.doctype + }, + callback: function(r) { + if (r && r.message) { + const doc = frappe.model.sync(r.message); + frappe.set_route("Form", doc[0].doctype, doc[0].name); + } + } + }); + }, __('Create')); + } + }, + onload: function(frm) { set_schedule_date(frm); if (!frm.doc.transaction_date){ @@ -67,7 +121,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e 'Purchase Receipt': 'Purchase Receipt', 'Purchase Invoice': 'Purchase Invoice', 'Payment Entry': 'Payment', - 'Subcontracting Order': 'Subcontracting Order' + 'Subcontracting Order': 'Subcontracting Order', + 'Stock Entry': 'Material to Supplier' } super.setup(); @@ -138,7 +193,14 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e if(flt(doc.per_received) < 100 && allow_receipt) { cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create')); if (doc.is_subcontracted) { - cur_frm.add_custom_button(__('Subcontracting Order'), this.make_subcontracting_order, __('Create')); + if (doc.is_old_subcontracting_flow) { + if (me.has_unsupplied_items()) { + cur_frm.add_custom_button(__('Material to Supplier'), function() { me.make_stock_entry(); }, __("Transfer")); + } + } + else { + cur_frm.add_custom_button(__('Subcontracting Order'), this.make_subcontracting_order, __('Create')); + } } } if(flt(doc.per_billed) < 100) @@ -206,6 +268,143 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e set_schedule_date(this.frm); } + has_unsupplied_items() { + return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty); + } + + make_stock_entry() { + var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; }); + var me = this; + + if(items.length >= 1){ + me.raw_material_data = []; + me.show_dialog = 1; + let title = __('Transfer Material to Supplier'); + let fields = [ + {fieldtype:'Section Break', label: __('Raw Materials')}, + {fieldname: 'sub_con_rm_items', fieldtype: 'Table', label: __('Items'), + fields: [ + { + fieldtype:'Data', + fieldname:'item_code', + label: __('Item'), + read_only:1, + in_list_view:1 + }, + { + fieldtype:'Data', + fieldname:'rm_item_code', + label: __('Raw Material'), + read_only:1, + in_list_view:1 + }, + { + fieldtype:'Float', + read_only:1, + fieldname:'qty', + label: __('Quantity'), + read_only:1, + in_list_view:1 + }, + { + fieldtype:'Data', + read_only:1, + fieldname:'warehouse', + label: __('Reserve Warehouse'), + in_list_view:1 + }, + { + fieldtype:'Float', + read_only:1, + fieldname:'rate', + label: __('Rate'), + hidden:1 + }, + { + fieldtype:'Float', + read_only:1, + fieldname:'amount', + label: __('Amount'), + hidden:1 + }, + { + fieldtype:'Link', + read_only:1, + fieldname:'uom', + label: __('UOM'), + hidden:1 + } + ], + data: me.raw_material_data, + get_data: function() { + return me.raw_material_data; + } + } + ] + + me.dialog = new frappe.ui.Dialog({ + title: title, fields: fields + }); + + if (me.frm.doc['supplied_items']) { + me.frm.doc['supplied_items'].forEach((item, index) => { + if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) { + me.raw_material_data.push ({ + 'name':item.name, + 'item_code': item.main_item_code, + 'rm_item_code': item.rm_item_code, + 'item_name': item.rm_item_code, + 'qty': item.required_qty - item.supplied_qty, + 'warehouse':item.reserve_warehouse, + 'rate':item.rate, + 'amount':item.amount, + 'stock_uom':item.stock_uom + }); + me.dialog.fields_dict.sub_con_rm_items.grid.refresh(); + } + }) + } + + me.dialog.get_field('sub_con_rm_items').check_all_rows() + + me.dialog.show() + this.dialog.set_primary_action(__('Transfer'), function() { + me.values = me.dialog.get_values(); + if(me.values) { + me.values.sub_con_rm_items.map((row,i) => { + if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) { + let row_id = i+1; + frappe.throw(__("Item Code, warehouse and quantity are required on row {0}", [row_id])); + } + }) + me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children()) + me.dialog.hide() + } + }); + } + + me.dialog.get_close_btn().on('click', () => { + me.dialog.hide(); + }); + + } + + _make_rm_stock_entry(rm_items) { + frappe.call({ + method:"erpnext.controllers.subcontracting_controller.make_rm_stock_entry", + args: { + subcontract_order: cur_frm.doc.name, + rm_items: rm_items, + order_doctype: cur_frm.doc.doctype + } + , + callback: function(r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + } + make_inter_company_order(frm) { frappe.model.open_mapped_doc({ method: "erpnext.buying.doctype.purchase_order.purchase_order.make_inter_company_sales_order", @@ -444,6 +643,20 @@ cur_frm.fields_dict['items'].grid.get_field('project').get_query = function(doc, } } +if (cur_frm.doc.is_old_subcontracting_flow) { + cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt, cdn) { + var d = locals[cdt][cdn] + return { + filters: [ + ['BOM', 'item', '=', d.item_code], + ['BOM', 'is_active', '=', '1'], + ['BOM', 'docstatus', '=', '1'], + ['BOM', 'company', '=', doc.company] + ] + } + } +} + function set_schedule_date(frm) { if(frm.doc.schedule_date){ erpnext.utils.copy_value_in_all_rows(frm.doc, frm.doc.doctype, frm.doc.name, "items", "schedule_date"); @@ -451,3 +664,9 @@ function set_schedule_date(frm) { } frappe.provide("erpnext.buying"); + +frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) { + if (frm.doc.is_old_subcontracting_flow) { + erpnext.buying.get_default_bom(frm); + } +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index b622b4f1be..aa50487d78 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -452,10 +452,6 @@ "options": "Warehouse", "print_hide": 1 }, - { - "fieldname": "col_break_warehouse", - "fieldtype": "Column Break" - }, { "default": "0", "fieldname": "is_subcontracted", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 6cf5837d8b..6f960a2c65 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -24,6 +24,7 @@ from erpnext.controllers.buying_controller import BuyingController from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.stock.doctype.item.item import get_item_defaults, get_last_purchase_details from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty +from erpnext.stock.utils import get_bin form_grid_templates = {"items": "templates/form_grid/item_grid.html"} @@ -68,6 +69,11 @@ class PurchaseOrder(BuyingController): self.validate_with_previous_doc() self.validate_for_subcontracting() self.validate_minimum_order_qty() + + if self.is_old_subcontracting_flow: + self.validate_bom_for_subcontracting_items() + self.create_raw_materials_supplied() + self.validate_fg_item_for_subcontracting() self.set_received_qty_for_drop_ship_items() validate_inter_company_party( @@ -191,8 +197,17 @@ class PurchaseOrder(BuyingController): ).format(item_code, qty, itemwise_min_order_qty.get(item_code)) ) + def validate_bom_for_subcontracting_items(self): + for item in self.items: + if not item.bom: + frappe.throw( + _("Row #{0}: BOM is not specified for subcontracting item {0}").format( + item.idx, item.item_code + ) + ) + def validate_fg_item_for_subcontracting(self): - if self.is_subcontracted: + if self.is_subcontracted and not self.is_old_subcontracting_flow: for item in self.items: if not item.fg_item: frappe.throw( @@ -207,6 +222,10 @@ class PurchaseOrder(BuyingController): "Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2}" ).format(item.idx, item.fg_item, item.item_code) ) + elif not frappe.get_value("Item", item.fg_item, "default_bom"): + frappe.throw( + _("Row #{0}: Default BOM not found for FG Item {1}").format(item.idx, item.fg_item) + ) if not item.fg_item_qty: frappe.throw( _("Row #{0}: Finished Good Item Qty is not specified for service item {0}").format( @@ -305,6 +324,7 @@ class PurchaseOrder(BuyingController): self.set_status(update=True, status=status) self.update_requested_qty() self.update_ordered_qty() + self.update_reserved_qty_for_subcontract() self.notify_update() clear_doctype_notifications(self) @@ -318,6 +338,7 @@ class PurchaseOrder(BuyingController): self.update_requested_qty() self.update_ordered_qty() self.validate_budget() + self.update_reserved_qty_for_subcontract() frappe.get_doc("Authorization Control").validate_approving_authority( self.doctype, self.company, self.base_grand_total @@ -337,6 +358,7 @@ class PurchaseOrder(BuyingController): if self.has_drop_ship_item(): self.update_delivered_qty_in_sales_order() + self.update_reserved_qty_for_subcontract() self.check_on_hold_or_closed_status() frappe.db.set(self, "status", "Cancelled") @@ -406,6 +428,13 @@ class PurchaseOrder(BuyingController): if item.delivered_by_supplier == 1: item.received_qty = item.qty + def update_reserved_qty_for_subcontract(self): + if self.is_old_subcontracting_flow: + for d in self.supplied_items: + if d.rm_item_code: + stock_bin = get_bin(d.rm_item_code, d.reserve_warehouse) + stock_bin.update_reserved_qty_for_sub_contracting(subcontract_doctype="Purchase Order") + def update_receiving_percentage(self): total_qty, received_qty = 0.0, 0.0 for item in self.items: @@ -649,4 +678,16 @@ def get_mapped_subcontracting_order(source_name, target_doc=None): target_doc.populate_items_table() + if target_doc.set_warehouse: + for item in target_doc.items: + item.warehouse = target_doc.set_warehouse + else: + source_doc = frappe.get_doc("Purchase Order", source_name) + if source_doc.set_warehouse: + for item in target_doc.items: + item.warehouse = source_doc.set_warehouse + else: + for idx, item in enumerate(target_doc.items): + item.warehouse = source_doc.items[idx].warehouse + return target_doc diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py index 0c38c3e8da..01b55c00d6 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py @@ -22,6 +22,6 @@ def get_data(): "label": _("Reference"), "items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"], }, - {"label": _("Sub-contracting"), "items": ["Subcontracting Order"]}, + {"label": _("Sub-contracting"), "items": ["Subcontracting Order", "Stock Entry"]}, ], } diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 12eef79dff..4794104740 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -574,18 +574,20 @@ "read_only": 1 }, { + "depends_on": "eval:parent.is_old_subcontracting_flow", "fieldname": "bom", "fieldtype": "Link", "label": "BOM", "options": "BOM", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "read_only_depends_on": "eval:!parent.is_old_subcontracting_flow" }, { "default": "0", + "depends_on": "eval:parent.is_old_subcontracting_flow", "fieldname": "include_exploded_items", "fieldtype": "Check", - "hidden": 1, "label": "Include Exploded Items", "print_hide": 1 }, @@ -849,27 +851,27 @@ "print_hide": 1 }, { - "depends_on": "eval:parent.is_subcontracted", + "depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow", "fieldname": "fg_item", "fieldtype": "Link", "label": "Finished Good Item", - "mandatory_depends_on": "eval:parent.is_subcontracted", + "mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow", "options": "Item" }, { "default": "1", - "depends_on": "eval:parent.is_subcontracted", + "depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow", "fieldname": "fg_item_qty", "fieldtype": "Float", "label": "Finished Good Item Qty", - "mandatory_depends_on": "eval:parent.is_subcontracted" + "mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-04-07 14:53:16.684010", + "modified": "2022-06-16 06:00:01.624317", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js index 57a41ad56c..075671f4ec 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js @@ -27,18 +27,16 @@ frappe.query_reports["Subcontract Order Summary"] = { reqd: 1 }, { - label: __("Subcontracting Order"), + label: __("Order Type"), + fieldname: "order_type", + fieldtype: "Select", + options: ["Purchase Order", "Subcontracting Order"], + default: "Subcontracting Order" + }, + { + label: __("Subcontract Order"), fieldname: "name", - fieldtype: "Link", - options: "Subcontracting Order", - get_query: function () { - return { - filters: { - docstatus: 1, - company: frappe.query_report.get_filter_value('company') - } - } - } + fieldtype: "Data" } ] }; \ No newline at end of file diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py index 3750daa71e..0213051aeb 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py @@ -8,7 +8,7 @@ from frappe import _ def execute(filters=None): columns, data = [], [] - columns = get_columns() + columns = get_columns(filters) data = get_data(filters) return columns, data @@ -20,42 +20,45 @@ def get_data(report_filters): if orders: supplied_items = get_supplied_items(orders, report_filters) - sco_details = prepare_subcontracted_data(orders, supplied_items) - get_subcontracted_data(sco_details, data) + order_details = prepare_subcontracted_data(orders, supplied_items) + get_subcontracted_data(order_details, data) return data def get_subcontracted_orders(report_filters): fields = [ - "`tabSubcontracting Order Item`.`parent` as sco_id", - "`tabSubcontracting Order Item`.`item_code`", - "`tabSubcontracting Order Item`.`item_name`", - "`tabSubcontracting Order Item`.`qty`", - "`tabSubcontracting Order Item`.`name`", - "`tabSubcontracting Order Item`.`received_qty`", - "`tabSubcontracting Order`.`status`", + f"`tab{report_filters.order_type} Item`.`parent` as order_id", + f"`tab{report_filters.order_type} Item`.`item_code`", + f"`tab{report_filters.order_type} Item`.`item_name`", + f"`tab{report_filters.order_type} Item`.`qty`", + f"`tab{report_filters.order_type} Item`.`name`", + f"`tab{report_filters.order_type} Item`.`received_qty`", + f"`tab{report_filters.order_type}`.`status`", ] filters = get_filters(report_filters) - return frappe.get_all("Subcontracting Order", fields=fields, filters=filters) or [] + return frappe.get_all(report_filters.order_type, fields=fields, filters=filters) or [] def get_filters(report_filters): filters = [ - ["Subcontracting Order", "docstatus", "=", 1], + [report_filters.order_type, "docstatus", "=", 1], [ - "Subcontracting Order", + report_filters.order_type, "transaction_date", "between", (report_filters.from_date, report_filters.to_date), ], ] + if report_filters.order_type == "Purchase Order": + filters.append(["Purchase Order", "is_old_subcontracting_flow", "=", 1]) + for field in ["name", "company"]: if report_filters.get(field): - filters.append(["Subcontracting Order", field, "=", report_filters.get(field)]) + filters.append([report_filters.order_type, field, "=", report_filters.get(field)]) return filters @@ -70,15 +73,21 @@ def get_supplied_items(orders, report_filters): "rm_item_code", "required_qty", "supplied_qty", + "returned_qty", "total_supplied_qty", "consumed_qty", "reference_name", ] - filters = {"parent": ("in", [d.sco_id for d in orders]), "docstatus": 1} + filters = {"parent": ("in", [d.order_id for d in orders]), "docstatus": 1} supplied_items = {} - for row in frappe.get_all("Subcontracting Order Supplied Item", fields=fields, filters=filters): + supplied_items_table = ( + "Purchase Order Item Supplied" + if report_filters.order_type == "Purchase Order" + else "Subcontracting Order Supplied Item" + ) + for row in frappe.get_all(supplied_items_table, fields=fields, filters=filters): new_key = (row.parent, row.reference_name, row.main_item_code) supplied_items.setdefault(new_key, []).append(row) @@ -87,24 +96,24 @@ def get_supplied_items(orders, report_filters): def prepare_subcontracted_data(orders, supplied_items): - sco_details = {} + order_details = {} for row in orders: - key = (row.sco_id, row.name, row.item_code) - if key not in sco_details: - sco_details.setdefault(key, frappe._dict({"sco_item": row, "supplied_items": []})) + key = (row.order_id, row.name, row.item_code) + if key not in order_details: + order_details.setdefault(key, frappe._dict({"order_item": row, "supplied_items": []})) - details = sco_details[key] + details = order_details[key] if supplied_items.get(key): for supplied_item in supplied_items[key]: details["supplied_items"].append(supplied_item) - return sco_details + return order_details -def get_subcontracted_data(sco_details, data): - for key, details in sco_details.items(): - res = details.sco_item +def get_subcontracted_data(order_details, data): + for key, details in order_details.items(): + res = details.order_item for index, row in enumerate(details.supplied_items): if index != 0: res = {} @@ -113,13 +122,13 @@ def get_subcontracted_data(sco_details, data): data.append(res) -def get_columns(): +def get_columns(filters): return [ { - "label": _("Subcontracting Order"), - "fieldname": "sco_id", + "label": _("Subcontract Order"), + "fieldname": "order_id", "fieldtype": "Link", - "options": "Subcontracting Order", + "options": filters.order_type, "width": 100, }, {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 80}, @@ -142,4 +151,5 @@ def get_columns(): {"label": _("Required Qty"), "fieldname": "required_qty", "fieldtype": "Float", "width": 110}, {"label": _("Supplied Qty"), "fieldname": "supplied_qty", "fieldtype": "Float", "width": 110}, {"label": _("Consumed Qty"), "fieldname": "consumed_qty", "fieldtype": "Float", "width": 120}, + {"label": _("Returned Qty"), "fieldname": "returned_qty", "fieldtype": "Float", "width": 110}, ] diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js index fc58b6aaaf..6304a0908d 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js +++ b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js @@ -4,6 +4,13 @@ frappe.query_reports["Subcontracted Item To Be Received"] = { "filters": [ + { + label: __("Order Type"), + fieldname: "order_type", + fieldtype: "Select", + options: ["Purchase Order", "Subcontracting Order"], + default: "Subcontracting Order" + }, { fieldname: "supplier", label: __("Supplier"), diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.json b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.json index fdf6cf702d..f40b788fe0 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.json +++ b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.json @@ -13,7 +13,7 @@ "name": "Subcontracted Item To Be Received", "owner": "Administrator", "prepared_report": 0, - "ref_doctype": "Purchase Order", + "ref_doctype": "Subcontracting Order", "report_name": "Subcontracted Item To Be Received", "report_type": "Script Report", "roles": [ diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py index 30f9dec4d0..135449bb2b 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py +++ b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py @@ -11,18 +11,18 @@ def execute(filters=None): frappe.msgprint(_("To Date must be greater than From Date")) data = [] - columns = get_columns() + columns = get_columns(filters) get_data(data, filters) return columns, data -def get_columns(): +def get_columns(filters): return [ { - "label": _("Subcontracting Order"), + "label": _("Subcontract Order"), "fieldtype": "Link", - "fieldname": "subcontracting_order", - "options": "Subcontracting Order", + "fieldname": "subcontract_order", + "options": filters.order_type, "width": 150, }, {"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "hidden": 1, "width": 150}, @@ -57,14 +57,14 @@ def get_columns(): def get_data(data, filters): - sco = get_sco(filters) - sco_name = [v.name for v in sco] - sub_items = get_subcontracting_order_item_supplied(sco_name) - for item in sub_items: - for order in sco: + orders = get_subcontract_orders(filters) + orders_name = [order.name for order in orders] + subcontracted_items = get_subcontract_order_supplied_item(filters.order_type, orders_name) + for item in subcontracted_items: + for order in orders: if order.name == item.parent and item.received_qty < item.qty: row = { - "subcontracting_order": item.parent, + "subcontract_order": item.parent, "date": order.transaction_date, "supplier": order.supplier, "fg_item_code": item.item_code, @@ -76,21 +76,25 @@ def get_data(data, filters): data.append(row) -def get_sco(filters): +def get_subcontract_orders(filters): record_filters = [ ["supplier", "=", filters.supplier], ["transaction_date", "<=", filters.to_date], ["transaction_date", ">=", filters.from_date], ["docstatus", "=", 1], ] + + if filters.order_type == "Purchase Order": + record_filters.append(["is_old_subcontracting_flow", "=", 1]) + return frappe.get_all( - "Subcontracting Order", filters=record_filters, fields=["name", "transaction_date", "supplier"] + filters.order_type, filters=record_filters, fields=["name", "transaction_date", "supplier"] ) -def get_subcontracting_order_item_supplied(sco): +def get_subcontract_order_supplied_item(order_type, orders): return frappe.get_all( - "Subcontracting Order Item", - filters=[("parent", "IN", sco)], + f"{order_type} Item", + filters=[("parent", "IN", orders)], fields=["parent", "item_code", "item_name", "qty", "received_qty"], ) diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py index 80fd657f41..c772c1a1b1 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py +++ b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py @@ -50,6 +50,7 @@ class TestSubcontractedItemToBeReceived(FrappeTestCase): col, data = execute( filters=frappe._dict( { + "order_type": "Subcontracting Order", "supplier": sco.supplier, "from_date": frappe.utils.get_datetime( frappe.utils.add_to_date(sco.transaction_date, days=-10) @@ -60,7 +61,7 @@ class TestSubcontractedItemToBeReceived(FrappeTestCase): ) self.assertEqual(data[0]["pending_qty"], 5) self.assertEqual(data[0]["received_qty"], 5) - self.assertEqual(data[0]["subcontracting_order"], sco.name) + self.assertEqual(data[0]["subcontract_order"], sco.name) self.assertEqual(data[0]["supplier"], sco.supplier) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js index 0853afd657..b6739fe663 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js @@ -4,6 +4,13 @@ frappe.query_reports["Subcontracted Raw Materials To Be Transferred"] = { "filters": [ + { + label: __("Order Type"), + fieldname: "order_type", + fieldtype: "Select", + options: ["Purchase Order", "Subcontracting Order"], + default: "Subcontracting Order" + }, { fieldname: "supplier", label: __("Supplier"), diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.json b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.json index c7cee5e20b..f689fbcf24 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.json +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.json @@ -13,7 +13,7 @@ "name": "Subcontracted Raw Materials To Be Transferred", "owner": "Administrator", "prepared_report": 0, - "ref_doctype": "Purchase Order", + "ref_doctype": "Subcontracting Order", "report_name": "Subcontracted Raw Materials To Be Transferred", "report_type": "Script Report", "roles": [ diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py index a837b24357..ef28eda62a 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py @@ -10,19 +10,19 @@ def execute(filters=None): if filters.from_date >= filters.to_date: frappe.msgprint(_("To Date must be greater than From Date")) - columns = get_columns() + columns = get_columns(filters) data = get_data(filters) return columns, data or [] -def get_columns(): +def get_columns(filters): return [ { - "label": _("Purchase Order"), + "label": _("Subcontract Order"), "fieldtype": "Link", - "fieldname": "purchase_order", - "options": "Purchase Order", + "fieldname": "subcontract_order", + "options": filters.order_type, "width": 200, }, {"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "width": 150}, @@ -46,10 +46,10 @@ def get_columns(): def get_data(filters): - sco_rm_item_details = get_sco_items_to_supply(filters) + order_rm_item_details = get_order_items_to_supply(filters) data = [] - for row in sco_rm_item_details: + for row in order_rm_item_details: transferred_qty = row.get("transferred_qty") or 0 if transferred_qty < row.get("reqd_qty", 0): pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty) @@ -59,22 +59,33 @@ def get_data(filters): return data -def get_sco_items_to_supply(filters): +def get_order_items_to_supply(filters): + supplied_items_table = ( + "Purchase Order Item Supplied" + if filters.order_type == "Purchase Order" + else "Subcontracting Order Supplied Item" + ) + + record_filters = [ + [filters.order_type, "per_received", "<", "100"], + [filters.order_type, "supplier", "=", filters.supplier], + [filters.order_type, "transaction_date", "<=", filters.to_date], + [filters.order_type, "transaction_date", ">=", filters.from_date], + [filters.order_type, "docstatus", "=", 1], + ] + + if filters.order_type == "Purchase Order": + record_filters.append([filters.order_type, "is_old_subcontracting_flow", "=", 1]) + return frappe.db.get_all( - "Subcontracting Order", + filters.order_type, fields=[ - "name as subcontracting_order", + "name as subcontract_order", "transaction_date as date", "supplier as supplier", - "`tabSubcontracting Order Supplied Item`.rm_item_code as rm_item_code", - "`tabSubcontracting Order Supplied Item`.required_qty as reqd_qty", - "`tabSubcontracting Order Supplied Item`.supplied_qty as transferred_qty", - ], - filters=[ - ["Subcontracting Order", "per_received", "<", "100"], - ["Subcontracting Order", "supplier", "=", filters.supplier], - ["Subcontracting Order", "transaction_date", "<=", filters.to_date], - ["Subcontracting Order", "transaction_date", ">=", filters.from_date], - ["Subcontracting Order", "docstatus", "=", 1], + f"`tab{supplied_items_table}`.rm_item_code as rm_item_code", + f"`tab{supplied_items_table}`.required_qty as reqd_qty", + f"`tab{supplied_items_table}`.supplied_qty as transferred_qty", ], + filters=record_filters, ) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py index d29791cebf..160295776b 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py @@ -9,14 +9,12 @@ from frappe.tests.utils import FrappeTestCase from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import ( execute, ) +from erpnext.controllers.subcontracting_controller import make_rm_stock_entry from erpnext.controllers.tests.test_subcontracting_controller import ( get_subcontracting_order, make_service_item, ) from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry -from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( - make_rm_stock_entry, -) class TestSubcontractedItemToBeTransferred(FrappeTestCase): @@ -45,6 +43,7 @@ class TestSubcontractedItemToBeTransferred(FrappeTestCase): col, data = execute( filters=frappe._dict( { + "order_type": "Subcontracting Order", "supplier": sco.supplier, "from_date": frappe.utils.get_datetime( frappe.utils.add_to_date(sco.transaction_date, days=-10) @@ -55,12 +54,12 @@ class TestSubcontractedItemToBeTransferred(FrappeTestCase): ) sco.reload() - sco_data = [row for row in data if row.get("subcontracting_order") == sco.name] + sco_data = [row for row in data if row.get("subcontract_order") == sco.name] # Alphabetically sort to be certain of order sco_data = sorted(sco_data, key=lambda i: i["rm_item_code"]) self.assertEqual(len(sco_data), 2) - self.assertEqual(sco_data[0]["subcontracting_order"], sco.name) + self.assertEqual(sco_data[0]["subcontract_order"], sco.name) self.assertEqual(sco_data[0]["rm_item_code"], "_Test Item") self.assertEqual(sco_data[0]["p_qty"], 8) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 7cb34fccf3..e83e0c2702 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2657,6 +2657,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.update_ordered_qty() parent.update_ordered_and_reserved_qty() parent.update_receiving_percentage() + if parent.is_old_subcontracting_flow: + parent.update_reserved_qty_for_subcontract() + parent.create_raw_materials_supplied() + parent.save() else: # Sales Order parent.validate_warehouse() parent.update_reserved_qty() diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index fa091df868..4db8ccb5b8 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -11,7 +11,7 @@ from erpnext.accounts.doctype.budget.budget import validate_expense_against_budg from erpnext.accounts.party import get_party_details from erpnext.buying.utils import update_last_purchase_rate, validate_for_items from erpnext.controllers.sales_and_purchase_return import get_rate_for_return -from erpnext.controllers.stock_controller import StockController +from erpnext.controllers.subcontracting_controller import SubcontractingController from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.utils import get_incoming_rate @@ -20,7 +20,10 @@ class QtyMismatchError(ValidationError): pass -class BuyingController(StockController): +class BuyingController(SubcontractingController): + def __setup__(self): + self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"] + def get_feed(self): if self.get("supplier_name"): return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total) @@ -51,6 +54,8 @@ class BuyingController(StockController): # sub-contracting self.validate_for_subcontracting() + if self.get("is_old_subcontracting_flow"): + self.create_raw_materials_supplied() self.set_landed_cost_voucher_amount() if self.doctype in ("Purchase Receipt", "Purchase Invoice"): @@ -251,9 +256,18 @@ class BuyingController(StockController): ) qty_in_stock_uom = flt(item.qty * item.conversion_factor) - item.valuation_rate = ( - item.base_net_amount + item.item_tax_amount + flt(item.landed_cost_voucher_amount) - ) / qty_in_stock_uom + if self.get("is_old_subcontracting_flow"): + item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate) + item.valuation_rate = ( + item.base_net_amount + + item.item_tax_amount + + item.rm_supp_cost + + flt(item.landed_cost_voucher_amount) + ) / qty_in_stock_uom + else: + item.valuation_rate = ( + item.base_net_amount + item.item_tax_amount + flt(item.landed_cost_voucher_amount) + ) / qty_in_stock_uom else: item.valuation_rate = 0.0 @@ -312,6 +326,19 @@ class BuyingController(StockController): if self.is_subcontracted: 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)) + + if self.get("is_old_subcontracting_flow"): + for item in self.get("items"): + 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)) + + if self.doctype != "Purchase Order": + return + + for row in self.get("supplied_items"): + 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(_(msg)) else: for item in self.get("items"): if item.get("bom"): @@ -440,7 +467,9 @@ class BuyingController(StockController): sle.update( { "incoming_rate": incoming_rate, - "recalculate_rate": 1 if (self.is_subcontracted and d.fg_item) or d.from_warehouse else 0, + "recalculate_rate": 1 + if (self.is_subcontracted and (d.bom or d.fg_item)) or d.from_warehouse + else 0, } ) sl_entries.append(sle) @@ -468,6 +497,8 @@ class BuyingController(StockController): ) ) + if self.get("is_old_subcontracting_flow"): + self.make_sl_entries_for_supplier_warehouse(sl_entries) self.make_sl_entries( sl_entries, allow_negative_stock=allow_negative_stock, @@ -494,6 +525,8 @@ class BuyingController(StockController): ) po_obj.update_ordered_qty(po_item_rows) + if self.get("is_old_subcontracting_flow"): + po_obj.update_reserved_qty_for_subcontract() def on_submit(self): if self.get("is_return"): @@ -718,7 +751,10 @@ class BuyingController(StockController): if self.doctype == "Material Request": return - validate_item_type(self, "is_purchase_item", "purchase") + if self.get("is_old_subcontracting_flow"): + validate_item_type(self, "is_sub_contracted_item", "subcontracted") + else: + validate_item_type(self, "is_purchase_item", "purchase") def get_asset_item_details(asset_items): diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 4e0d91147e..2a2f8f562e 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -2,6 +2,7 @@ # For license information, please see license.txt import copy +import json from collections import defaultdict import frappe @@ -14,13 +15,40 @@ from erpnext.stock.utils import get_incoming_rate class SubcontractingController(StockController): + def __init__(self, *args, **kwargs): + super(SubcontractingController, self).__init__(*args, **kwargs) + if self.get("is_old_subcontracting_flow"): + self.subcontract_data = frappe._dict( + { + "order_doctype": "Purchase Order", + "order_field": "purchase_order", + "rm_detail_field": "po_detail", + "receipt_supplied_items_field": "Purchase Receipt Item Supplied", + "order_supplied_items_field": "Purchase Order Item Supplied", + } + ) + else: + self.subcontract_data = frappe._dict( + { + "order_doctype": "Subcontracting Order", + "order_field": "subcontracting_order", + "rm_detail_field": "sco_rm_detail", + "receipt_supplied_items_field": "Subcontracting Receipt Supplied Item", + "order_supplied_items_field": "Subcontracting Order Supplied Item", + } + ) + def before_validate(self): - self.remove_empty_rows() - self.set_items_conversion_factor() + if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]: + self.remove_empty_rows() + self.set_items_conversion_factor() def validate(self): - self.validate_items() - self.create_raw_materials_supplied() + if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]: + self.validate_items() + self.create_raw_materials_supplied() + else: + super(SubcontractingController, self).validate() def remove_empty_rows(self): for key in ["service_items", "items", "supplied_items"]: @@ -54,7 +82,10 @@ class SubcontractingController(StockController): def __get_data_before_save(self): item_dict = {} - if self.doctype == "Subcontracting Receipt" and self._doc_before_save: + if ( + self.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"] + and self._doc_before_save + ): for row in self._doc_before_save.get("items"): item_dict[row.name] = (row.item_code, row.qty) @@ -64,7 +95,7 @@ class SubcontractingController(StockController): self.__changed_name = [] self.__reference_name = [] - if self.doctype == "Subcontracting Order" or self.is_new(): + if self.doctype in ["Purchase Order", "Subcontracting Order"] or self.is_new(): self.set(self.raw_material_table, []) return @@ -93,36 +124,38 @@ class SubcontractingController(StockController): self.alternative_item_details = frappe._dict() self.__get_backflush_based_on() - def __get_subcontracting_orders(self): - self.subcontracting_orders = [] + def __get_subcontract_orders(self): + self.subcontract_orders = [] - if self.doctype == "Subcontracting Order": + if self.doctype in ["Purchase Order", "Subcontracting Order"]: return - self.subcontracting_orders = [ - item.subcontracting_order for item in self.items if item.subcontracting_order + self.subcontract_orders = [ + item.get(self.subcontract_data.order_field) + for item in self.items + if item.get(self.subcontract_data.order_field) ] def __get_pending_qty_to_receive(self): - """Get qty to be received against the subcontracting order.""" + """Get qty to be received against the subcontract order.""" self.qty_to_be_received = defaultdict(float) if ( - self.doctype != "Subcontracting Order" + self.doctype != self.subcontract_data.order_doctype and self.backflush_based_on != "BOM" - and self.subcontracting_orders + and self.subcontract_orders ): for row in frappe.get_all( - "Subcontracting Order Item", + f"{self.subcontract_data.order_doctype} Item", fields=["item_code", "(qty - received_qty) as qty", "parent", "name"], - filters={"docstatus": 1, "parent": ("in", self.subcontracting_orders)}, + filters={"docstatus": 1, "parent": ("in", self.subcontract_orders)}, ): self.qty_to_be_received[(row.item_code, row.parent)] += row.qty def __get_transferred_items(self): - fields = ["`tabStock Entry`.`subcontracting_order`"] + fields = [f"`tabStock Entry`.`{self.subcontract_data.order_field}`"] alias_dict = { "item_code": "rm_item_code", "subcontracted_item": "main_item_code", @@ -145,7 +178,7 @@ class SubcontractingController(StockController): "s_warehouse", "t_warehouse", "item_group", - "sco_rm_detail", + self.subcontract_data.rm_detail_field, ] if self.backflush_based_on == "BOM": @@ -157,7 +190,7 @@ class SubcontractingController(StockController): filters = [ ["Stock Entry", "docstatus", "=", 1], ["Stock Entry", "purpose", "=", "Send to Subcontractor"], - ["Stock Entry", "subcontracting_order", "in", self.subcontracting_orders], + ["Stock Entry", self.subcontract_data.order_field, "in", self.subcontract_orders], ] return frappe.get_all("Stock Entry", fields=fields, filters=filters) @@ -168,21 +201,21 @@ class SubcontractingController(StockController): def __get_received_items(self, doctype): fields = [] - self.sco_field = "subcontracting_order" - - for field in ["name", self.sco_field, "parent"]: + for field in ["name", self.subcontract_data.order_field, "parent"]: fields.append(f"`tab{doctype} Item`.`{field}`") filters = [ [doctype, "docstatus", "=", 1], - [f"{doctype} Item", self.sco_field, "in", self.subcontracting_orders], + [f"{doctype} Item", self.subcontract_data.order_field, "in", self.subcontract_orders], ] + if doctype == "Purchase Invoice": + filters.append(["Purchase Invoice", "update_stock", "=", 1]) return frappe.get_all(f"{doctype}", fields=fields, filters=filters) - def __get_consumed_items(self, doctype, scr_items): + def __get_consumed_items(self, doctype, receipt_items): return frappe.get_all( - "Subcontracting Receipt Supplied Item", + self.subcontract_data.receipt_supplied_items_field, fields=[ "serial_no", "rm_item_code", @@ -191,26 +224,26 @@ class SubcontractingController(StockController): "consumed_qty", "main_item_code", ], - filters={"docstatus": 1, "reference_name": ("in", list(scr_items)), "parenttype": doctype}, + filters={"docstatus": 1, "reference_name": ("in", list(receipt_items)), "parenttype": doctype}, ) def __update_consumed_materials(self, doctype, return_consumed_items=False): """Deduct the consumed materials from the available materials.""" - scr_items = self.__get_received_items(doctype) - if not scr_items: + receipt_items = self.__get_received_items(doctype) + if not receipt_items: return ([], {}) if return_consumed_items else None - scr_items = { - item.name: item.get(self.get("sco_field") or "subcontracting_order") for item in scr_items + receipt_items = { + item.name: item.get(self.subcontract_data.order_field) for item in receipt_items } - consumed_materials = self.__get_consumed_items(doctype, scr_items.keys()) + consumed_materials = self.__get_consumed_items(doctype, receipt_items.keys()) if return_consumed_items: - return (consumed_materials, scr_items) + return (consumed_materials, receipt_items) for row in consumed_materials: - key = (row.rm_item_code, row.main_item_code, scr_items.get(row.reference_name)) + key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name)) if not self.available_materials.get(key): continue @@ -226,16 +259,16 @@ class SubcontractingController(StockController): def get_available_materials(self): """Get the available raw materials which has been transferred to the supplier. available_materials = { - (item_code, subcontracted_item, subcontracting_order): { + (item_code, subcontracted_item, subcontract_order): { 'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details } } """ - if not self.subcontracting_orders: + if not self.subcontract_orders: return for row in self.__get_transferred_items(): - key = (row.rm_item_code, row.main_item_code, row.subcontracting_order) + key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field)) if key not in self.available_materials: self.available_materials.setdefault( @@ -246,14 +279,16 @@ class SubcontractingController(StockController): "serial_no": [], "batch_no": defaultdict(float), "item_details": row, - "sco_rm_details": [], + f"{self.subcontract_data.rm_detail_field}s": [], } ), ) details = self.available_materials[key] details.qty += row.qty - details.sco_rm_details.append(row.sco_rm_detail) + details[f"{self.subcontract_data.rm_detail_field}s"].append( + row.get(self.subcontract_data.rm_detail_field) + ) if row.serial_no: details.serial_no.extend(get_serial_nos(row.serial_no)) @@ -264,7 +299,11 @@ class SubcontractingController(StockController): self.__set_alternative_item_details(row) self.__transferred_items = copy.deepcopy(self.available_materials) - self.__update_consumed_materials("Subcontracting Receipt") + if self.get("is_old_subcontracting_flow"): + for doctype in ["Purchase Receipt", "Purchase Invoice"]: + self.__update_consumed_materials(doctype) + else: + self.__update_consumed_materials("Subcontracting Receipt") def __remove_changed_rows(self): if not self.__changed_name: @@ -317,7 +356,7 @@ class SubcontractingController(StockController): ) def __update_reserve_warehouse(self, row, item): - if self.doctype == "Subcontracting Order": + if self.doctype == self.subcontract_data.order_doctype: row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse def __set_alternative_item(self, bom_item): @@ -325,7 +364,7 @@ class SubcontractingController(StockController): bom_item.update(self.alternative_item_details[bom_item.rm_item_code]) def __set_serial_nos(self, item_row, rm_obj): - key = (rm_obj.rm_item_code, item_row.item_code, item_row.subcontracting_order) + key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field)) if self.available_materials.get(key) and self.available_materials[key]["serial_no"]: used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)] rm_obj.serial_no = "\n".join(used_serial_nos) @@ -340,7 +379,7 @@ class SubcontractingController(StockController): "consumed_qty": qty, "batch_no": batch_no, "required_qty": qty, - "subcontracting_order": item_row.subcontracting_order, + self.subcontract_data.order_field: item_row.get(self.subcontract_data.order_field), } ) @@ -351,7 +390,7 @@ class SubcontractingController(StockController): rm_obj.consumed_qty = consumed_qty def __set_batch_nos(self, bom_item, item_row, rm_obj, qty): - key = (rm_obj.rm_item_code, item_row.item_code, item_row.subcontracting_order) + key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field)) if self.available_materials.get(key) and self.available_materials[key]["batch_no"]: new_rm_obj = None @@ -397,16 +436,18 @@ class SubcontractingController(StockController): ) rm_obj.rate = get_incoming_rate(args) - if self.doctype == "Subcontracting Order": + if self.doctype == self.subcontract_data.order_doctype: rm_obj.required_qty = qty rm_obj.amount = rm_obj.required_qty * rm_obj.rate else: rm_obj.consumed_qty = 0 - rm_obj.subcontracting_order = item_row.subcontracting_order + setattr( + rm_obj, self.subcontract_data.order_field, item_row.get(self.subcontract_data.order_field) + ) self.__set_batch_nos(bom_item, item_row, rm_obj, qty) def __get_qty_based_on_material_transfer(self, item_row, transfer_item): - key = (item_row.item_code, item_row.subcontracting_order) + key = (item_row.item_code, item_row.get(self.subcontract_data.order_field)) if self.qty_to_be_received == item_row.qty: return transfer_item.qty @@ -427,13 +468,13 @@ class SubcontractingController(StockController): has_supplied_items = True if self.get(self.raw_material_table) else False for row in self.items: - if self.doctype != "Subcontracting Order" and ( + if self.doctype != self.subcontract_data.order_doctype and ( (self.__changed_name and row.name not in self.__changed_name) or (has_supplied_items and not self.__changed_name) ): continue - if self.doctype == "Subcontracting Order" or self.backflush_based_on == "BOM": + if self.doctype == self.subcontract_data.order_doctype or self.backflush_based_on == "BOM": for bom_item in self.__get_materials_from_bom( row.item_code, row.bom, row.get("include_exploded_items") ): @@ -445,17 +486,22 @@ class SubcontractingController(StockController): elif self.backflush_based_on != "BOM": for key, transfer_item in self.available_materials.items(): - if (key[1], key[2]) == (row.item_code, row.subcontracting_order) and transfer_item.qty > 0: + if (key[1], key[2]) == ( + row.item_code, + row.get(self.subcontract_data.order_field), + ) and transfer_item.qty > 0: qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0 transfer_item.qty -= qty self.__add_supplied_item(row, transfer_item.get("item_details"), qty) if self.qty_to_be_received: - self.qty_to_be_received[(row.item_code, row.subcontracting_order)] -= row.qty + self.qty_to_be_received[ + (row.item_code, row.get(self.subcontract_data.order_field)) + ] -= row.qty def __prepare_supplied_items(self): self.initialized_fields() - self.__get_subcontracting_orders() + self.__get_subcontract_orders() self.__get_pending_qty_to_receive() self.get_available_materials() self.__remove_changed_rows() @@ -465,8 +511,10 @@ class SubcontractingController(StockController): if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get( "batch_no" ): - link = get_link_to_form("Subcontracting Order", row.subcontracting_order) - msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Subcontracting Order {link}' + link = get_link_to_form( + self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field) + ) + msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the {self.subcontract_data.order_doctype} {link}' frappe.throw(_(msg), title=_("Incorrect Batch Consumed")) def __validate_serial_no(self, row, key): @@ -476,16 +524,18 @@ class SubcontractingController(StockController): if incorrect_sn: incorrect_sn = "\n".join(incorrect_sn) - link = get_link_to_form("Subcontracting Order", row.subcontracting_order) - msg = f"The Serial Nos {incorrect_sn} has not supplied against the Subcontracting Order {link}" + link = get_link_to_form( + self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field) + ) + msg = f"The Serial Nos {incorrect_sn} has not supplied against the {self.subcontract_data.order_doctype} {link}" frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed")) def __validate_supplied_items(self): - if self.doctype != "Subcontracting Receipt": + if self.doctype not in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]: return for row in self.get(self.raw_material_table): - key = (row.rm_item_code, row.main_item_code, row.subcontracting_order) + key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field)) if not self.__transferred_items or not self.__transferred_items.get(key): return @@ -493,6 +543,9 @@ class SubcontractingController(StockController): self.__validate_serial_no(row, key) def set_materials_for_subcontracted_items(self, raw_material_table): + if self.doctype == "Purchase Invoice" and not self.update_stock: + return + self.raw_material_table = raw_material_table self.__identify_change_in_item_table() self.__prepare_supplied_items() @@ -501,16 +554,16 @@ class SubcontractingController(StockController): def create_raw_materials_supplied(self, raw_material_table="supplied_items"): self.set_materials_for_subcontracted_items(raw_material_table) - if self.doctype == "Subcontracting Receipt": + if self.doctype in ["Subcontracting Receipt", "Purchase Receipt", "Purchase Invoice"]: for item in self.get("items"): item.rm_supp_cost = 0.0 - def __update_consumed_qty_in_sco(self, itemwise_consumed_qty): + def __update_consumed_qty_in_subcontract_order(self, itemwise_consumed_qty): fields = ["main_item_code", "rm_item_code", "parent", "supplied_qty", "name"] - filters = {"docstatus": 1, "parent": ("in", self.subcontracting_orders)} + filters = {"docstatus": 1, "parent": ("in", self.subcontract_orders)} for row in frappe.get_all( - "Subcontracting Order Supplied Item", fields=fields, filters=filters, order_by="idx" + self.subcontract_data.order_supplied_items_field, fields=fields, filters=filters, order_by="idx" ): key = (row.rm_item_code, row.main_item_code, row.parent) consumed_qty = itemwise_consumed_qty.get(key, 0) @@ -520,22 +573,31 @@ class SubcontractingController(StockController): itemwise_consumed_qty[key] -= consumed_qty frappe.db.set_value( - "Subcontracting Order Supplied Item", row.name, "consumed_qty", consumed_qty + self.subcontract_data.order_supplied_items_field, row.name, "consumed_qty", consumed_qty ) - def set_consumed_qty_in_sco(self): - # Update consumed qty back in the subcontracting order - self.__get_subcontracting_orders() - itemwise_consumed_qty = defaultdict(float) - consumed_items, scr_items = self.__update_consumed_materials( - "Subcontracting Receipt", return_consumed_items=True - ) + def set_consumed_qty_in_subcontract_order(self): + # Update consumed qty back in the subcontract order + if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"] or self.get( + "is_old_subcontracting_flow" + ): + self.__get_subcontract_orders() + itemwise_consumed_qty = defaultdict(float) + if self.get("is_old_subcontracting_flow"): + doctypes = ["Purchase Receipt", "Purchase Invoice"] + else: + doctypes = ["Subcontracting Receipt"] - for row in consumed_items: - key = (row.rm_item_code, row.main_item_code, scr_items.get(row.reference_name)) - itemwise_consumed_qty[key] += row.consumed_qty + for doctype in doctypes: + consumed_items, receipt_items = self.__update_consumed_materials( + doctype, return_consumed_items=True + ) - self.__update_consumed_qty_in_sco(itemwise_consumed_qty) + for row in consumed_items: + key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name)) + itemwise_consumed_qty[key] += row.consumed_qty + + self.__update_consumed_qty_in_subcontract_order(itemwise_consumed_qty) def update_ordered_and_reserved_qty(self): sco_map = {} @@ -618,10 +680,30 @@ class SubcontractingController(StockController): via_landed_cost_voucher=via_landed_cost_voucher, ) - def get_supplied_items_cost(self, item_row_id): + def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True): supplied_items_cost = 0.0 for item in self.get("supplied_items"): if item.reference_name == item_row_id: + if ( + self.get("is_old_subcontracting_flow") + and reset_outgoing_rate + and frappe.get_cached_value("Item", item.rm_item_code, "is_stock_item") + ): + rate = get_incoming_rate( + { + "item_code": item.rm_item_code, + "warehouse": self.supplier_warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "qty": -1 * item.consumed_qty, + "serial_no": item.serial_no, + "batch_no": item.batch_no, + } + ) + + if rate > 0: + item.rate = rate + item.amount = flt(flt(item.consumed_qty) * flt(item.rate), item.precision("amount")) supplied_items_cost += item.amount @@ -631,13 +713,25 @@ class SubcontractingController(StockController): if self.doctype == "Subcontracting Order": self.update_status() elif self.doctype == "Subcontracting Receipt": - self.__get_subcontracting_orders + self.__get_subcontract_orders - if self.subcontracting_orders: - for sco in set(self.subcontracting_orders): + if self.subcontract_orders: + for sco in set(self.subcontract_orders): sco_doc = frappe.get_doc("Subcontracting Order", sco) sco_doc.update_status() + @frappe.whitelist() + def get_current_stock(self): + if self.doctype in ["Purchase Receipt", "Subcontracting Receipt"]: + for item in self.get("supplied_items"): + if self.supplier_warehouse: + actual_qty = frappe.db.get_value( + "Bin", + {"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse}, + "actual_qty", + ) + item.current_stock = flt(actual_qty) or 0 + @property def sub_contracted_items(self): if not hasattr(self, "_sub_contracted_items"): @@ -650,3 +744,159 @@ class SubcontractingController(StockController): self._sub_contracted_items = [item.name for item in items] return self._sub_contracted_items + + +def get_item_details(items): + item = frappe.qb.DocType("Item") + item_list = ( + frappe.qb.from_(item) + .select(item.item_code, item.description, item.allow_alternative_item) + .where(item.name.isin(items)) + .run(as_dict=True) + ) + + item_details = {} + for item in item_list: + item_details[item.item_code] = item + + return item_details + + +@frappe.whitelist() +def make_rm_stock_entry(subcontract_order, rm_items, order_doctype="Subcontracting Order"): + rm_items_list = rm_items + + if isinstance(rm_items, str): + rm_items_list = json.loads(rm_items) + elif not rm_items: + frappe.throw(_("No Items available for transfer")) + + if rm_items_list: + fg_items = list(set(item["item_code"] for item in rm_items_list)) + else: + frappe.throw(_("No Items selected for transfer")) + + if subcontract_order: + subcontract_order = frappe.get_doc(order_doctype, subcontract_order) + + if fg_items: + items = tuple(set(item["rm_item_code"] for item in rm_items_list)) + item_wh = get_item_details(items) + + stock_entry = frappe.new_doc("Stock Entry") + stock_entry.purpose = "Send to Subcontractor" + if order_doctype == "Purchase Order": + stock_entry.purchase_order = subcontract_order.name + else: + stock_entry.subcontracting_order = subcontract_order.name + stock_entry.supplier = subcontract_order.supplier + stock_entry.supplier_name = subcontract_order.supplier_name + stock_entry.supplier_address = subcontract_order.supplier_address + stock_entry.address_display = subcontract_order.address_display + stock_entry.company = subcontract_order.company + stock_entry.to_warehouse = subcontract_order.supplier_warehouse + stock_entry.set_stock_entry_type() + + if order_doctype == "Purchase Order": + rm_detail_field = "po_detail" + else: + rm_detail_field = "sco_rm_detail" + + for item_code in fg_items: + for rm_item_data in rm_items_list: + if rm_item_data["item_code"] == item_code: + rm_item_code = rm_item_data["rm_item_code"] + items_dict = { + rm_item_code: { + rm_detail_field: rm_item_data.get("name"), + "item_name": rm_item_data["item_name"], + "description": item_wh.get(rm_item_code, {}).get("description", ""), + "qty": rm_item_data["qty"], + "from_warehouse": rm_item_data["warehouse"], + "stock_uom": rm_item_data["stock_uom"], + "serial_no": rm_item_data.get("serial_no"), + "batch_no": rm_item_data.get("batch_no"), + "main_item_code": rm_item_data["item_code"], + "allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"), + } + } + stock_entry.add_to_stock_entry_detail(items_dict) + return stock_entry.as_dict() + else: + frappe.throw(_("No Items selected for transfer")) + return subcontract_order.name + + +def add_items_in_ste( + ste_doc, row, qty, rm_details, rm_detail_field="sco_rm_detail", batch_no=None +): + item = ste_doc.append("items", row.item_details) + + rm_detail = list(set(row.get(f"{rm_detail_field}s")).intersection(rm_details)) + item.update( + { + "qty": qty, + "batch_no": batch_no, + "basic_rate": row.item_details["rate"], + rm_detail_field: rm_detail[0] if rm_detail else "", + "s_warehouse": row.item_details["t_warehouse"], + "t_warehouse": row.item_details["s_warehouse"], + "item_code": row.item_details["rm_item_code"], + "subcontracted_item": row.item_details["main_item_code"], + "serial_no": "\n".join(row.serial_no) if row.serial_no else "", + } + ) + + +def make_return_stock_entry_for_subcontract( + available_materials, order_doc, rm_details, order_doctype="Subcontracting Order" +): + ste_doc = frappe.new_doc("Stock Entry") + ste_doc.purpose = "Material Transfer" + + if order_doctype == "Purchase Order": + ste_doc.purchase_order = order_doc.name + rm_detail_field = "po_detail" + else: + ste_doc.subcontracting_order = order_doc.name + rm_detail_field = "sco_rm_detail" + ste_doc.company = order_doc.company + ste_doc.is_return = 1 + + for key, value in available_materials.items(): + if not value.qty: + continue + + if value.batch_no: + for batch_no, qty in value.batch_no.items(): + if qty > 0: + add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field, batch_no) + else: + add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field) + + ste_doc.set_stock_entry_type() + ste_doc.calculate_rate_and_amount() + + return ste_doc + + +@frappe.whitelist() +def get_materials_from_supplier( + subcontract_order, rm_details, order_doctype="Subcontracting Order" +): + if isinstance(rm_details, str): + rm_details = json.loads(rm_details) + + doc = frappe.get_cached_doc(order_doctype, subcontract_order) + doc.initialized_fields() + doc.subcontract_orders = [doc.name] + doc.get_available_materials() + + if not doc.available_materials: + frappe.throw( + _("Materials are already received against the {0} {1}").format(order_doctype, subcontract_order) + ) + + return make_return_stock_entry_for_subcontract( + doc.available_materials, doc, rm_details, order_doctype + ) diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index 4ef3d649df..4fab8058b8 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -9,13 +9,15 @@ from frappe.tests.utils import FrappeTestCase from frappe.utils import cint from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order +from erpnext.controllers.subcontracting_controller import ( + get_materials_from_supplier, + make_rm_stock_entry, +) from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( - get_materials_from_supplier, - make_rm_stock_entry, make_subcontracting_receipt, ) diff --git a/erpnext/patches/v13_0/add_bin_unique_constraint.py b/erpnext/patches/v13_0/add_bin_unique_constraint.py index 38a8500ac7..7ad2bec859 100644 --- a/erpnext/patches/v13_0/add_bin_unique_constraint.py +++ b/erpnext/patches/v13_0/add_bin_unique_constraint.py @@ -64,4 +64,8 @@ def delete_and_patch_duplicate_bins(): bin.update(qty_dict) bin.update_reserved_qty_for_production() bin.update_reserved_qty_for_sub_contracting() + if frappe.db.count( + "Purchase Order", {"status": ["!=", "Completed"], "is_old_subcontracting_flow": 1} + ): + bin.update_reserved_qty_for_sub_contracting(subcontract_doctype="Purchase Order") bin.db_update() diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 91e0771654..09779d89ec 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -83,9 +83,17 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac this.frm.set_query("item_code", "items", function() { if (me.frm.doc.is_subcontracted) { + var filters = {'supplier': me.frm.doc.supplier}; + if (me.frm.doc.is_old_subcontracting_flow) { + filters["is_sub_contracted_item"] = 1; + } + else { + filters["is_stock_item"] = 0; + } + return{ query: "erpnext.controllers.queries.item_query", - filters:{ 'supplier': me.frm.doc.supplier, 'is_stock_item': 0 } + filters: filters } } else { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index de93c82ef2..d86ff1c50f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -471,7 +471,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe cost_center: item.cost_center, tax_category: me.frm.doc.tax_category, item_tax_template: item.item_tax_template, - child_docname: item.name + child_docname: item.name, + is_old_subcontracting_flow: me.frm.doc.is_old_subcontracting_flow, } }, diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 01710f1e41..68b3e2e20a 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -486,7 +486,11 @@ erpnext.utils.update_child_items = function(opts) { filters = {"is_sales_item": 1}; } else if (frm.doc.doctype == 'Purchase Order') { if (frm.doc.is_subcontracted) { - filters = {"is_sub_contracted_item": 1}; + if (frm.doc.is_old_subcontracting_flow) { + filters = {"is_sub_contracted_item": 1}; + } else { + filters = {"is_stock_item": 0}; + } } else { filters = {"is_purchase_item": 1}; } diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 448b0496eb..548df318fa 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -40,23 +40,37 @@ class Bin(Document): self.db_set("reserved_qty_for_production", flt(self.reserved_qty_for_production)) self.db_set("projected_qty", self.projected_qty) - def update_reserved_qty_for_sub_contracting(self): + def update_reserved_qty_for_sub_contracting(self, subcontract_doctype="Subcontracting Order"): # reserved qty - sco = frappe.qb.DocType("Subcontracting Order") - supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item") + subcontract_order = frappe.qb.DocType(subcontract_doctype) + supplied_item = frappe.qb.DocType( + "Purchase Order Item Supplied" + if subcontract_doctype == "Purchase Order" + else "Subcontracting Order Supplied Item" + ) + + conditions = ( + (supplied_item.rm_item_code == self.item_code) + & (subcontract_order.name == supplied_item.parent) + & (subcontract_order.per_received < 100) + & (supplied_item.reserve_warehouse == self.warehouse) + & ( + ( + (subcontract_order.is_old_subcontracting_flow == 1) + & (subcontract_order.status != "Closed") + & (subcontract_order.docstatus == 1) + ) + if subcontract_doctype == "Purchase Order" + else (subcontract_order.docstatus == 1) + ) + ) reserved_qty_for_sub_contract = ( - frappe.qb.from_(sco) + frappe.qb.from_(subcontract_order) .from_(supplied_item) .select(Sum(Coalesce(supplied_item.required_qty, 0))) - .where( - (supplied_item.rm_item_code == self.item_code) - & (sco.name == supplied_item.parent) - & (sco.docstatus == 1) - & (sco.per_received < 100) - & (supplied_item.reserve_warehouse == self.warehouse) - ) + .where(conditions) ).run()[0][0] or 0.0 se = frappe.qb.DocType("Stock Entry") @@ -69,23 +83,34 @@ class Bin(Document): else: qty_field = se_item.transfer_qty + conditions = ( + (se.docstatus == 1) + & (se.purpose == "Send to Subcontractor") + & ((se_item.item_code == self.item_code) | (se_item.original_item == self.item_code)) + & (se.name == se_item.parent) + & (subcontract_order.docstatus == 1) + & (subcontract_order.per_received < 100) + & ( + ( + (Coalesce(se.purchase_order, "") != "") + & (subcontract_order.name == se.purchase_order) + & (subcontract_order.is_old_subcontracting_flow == 1) + & (subcontract_order.status != "Closed") + ) + if subcontract_doctype == "Purchase Order" + else ( + (Coalesce(se.subcontracting_order, "") != "") + & (subcontract_order.name == se.subcontracting_order) + ) + ) + ) + materials_transferred = ( frappe.qb.from_(se) .from_(se_item) - .from_(sco) - .select( - Sum(Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty)) - ) - .where( - (se.docstatus == 1) - & (se.purpose == "Send to Subcontractor") - & (Coalesce(se.subcontracting_order, "") != "") - & ((se_item.item_code == self.item_code) | (se_item.original_item == self.item_code)) - & (se.name == se_item.parent) - & (sco.name == se.subcontracting_order) - & (sco.docstatus == 1) - & (sco.per_received < 100) - ) + .from_(subcontract_order) + .select(Sum(qty_field)) + .where(conditions) ).run()[0][0] or 0.0 if reserved_qty_for_sub_contract > materials_transferred: diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py index 49530b4bb3..199641803e 100644 --- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py @@ -5,6 +5,7 @@ import frappe from frappe.tests.utils import FrappeTestCase from frappe.utils import flt +from erpnext.controllers.subcontracting_controller import make_rm_stock_entry from erpnext.controllers.tests.test_subcontracting_controller import ( get_subcontracting_order, make_service_item, @@ -21,7 +22,6 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation, ) from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( - make_rm_stock_entry, make_subcontracting_receipt, ) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 74db616b2b..e6fcb78f12 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -296,6 +296,10 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt frappe.provide("erpnext.buying"); frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) { + if (frm.doc.is_old_subcontracting_flow) { + erpnext.buying.get_default_bom(frm); + } + frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted); }); diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index ff4e0a13cf..cef9ddda97 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -123,6 +123,7 @@ class PurchaseReceipt(BuyingController): if getdate(self.posting_date) > getdate(nowdate()): throw(_("Posting Date cannot be future date")) + self.get_current_stock() self.reset_default_field_value("set_warehouse", "items", "warehouse") self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse") self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse") @@ -234,6 +235,7 @@ class PurchaseReceipt(BuyingController): self.make_gl_entries() self.repost_future_sle_and_gle() + self.set_consumed_qty_in_subcontract_order() def check_next_docstatus(self): submit_rv = frappe.db.sql( @@ -269,6 +271,7 @@ class PurchaseReceipt(BuyingController): self.repost_future_sle_and_gle() self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation") self.delete_auto_created_batches() + self.set_consumed_qty_in_subcontract_order() def get_gl_entries(self, warehouse_account=None): from erpnext.accounts.general_ledger import process_gl_map diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 625a3037c1..f0de04b161 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -642,13 +642,15 @@ "print_hide": 1 }, { + "depends_on": "eval:parent.is_old_subcontracting_flow", "fieldname": "bom", "fieldtype": "Link", "label": "BOM", "no_copy": 1, "options": "BOM", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "read_only_depends_on": "eval:!parent.is_old_subcontracting_flow" }, { "default": "0", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index a838236f2e..1c514a90ee 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -615,8 +615,15 @@ frappe.ui.form.on('Stock Entry', { if (frm.doc.apply_putaway_rule) erpnext.apply_putaway_rule(frm, frm.doc.purpose); }, + purchase_order: (frm) => { + if (frm.doc.purchase_order) { + frm.set_value("subcontracting_order", ""); + } + }, + subcontracting_order: (frm) => { if (frm.doc.subcontracting_order) { + frm.set_value("purchase_order", ""); erpnext.utils.map_current_doc({ method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order', source_name: frm.doc.subcontracting_order, @@ -624,9 +631,6 @@ frappe.ui.form.on('Stock Entry', { freeze: true, }); } - else { - frm.set_value("items", []); - } }, }); @@ -790,6 +794,16 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle return erpnext.queries.item({is_stock_item: 1}); }; + this.frm.set_query("purchase_order", function() { + return { + "filters": { + "docstatus": 1, + "is_old_subcontracting_flow": 1, + "company": me.frm.doc.company + } + }; + }); + this.frm.set_query("subcontracting_order", function() { return { "filters": { @@ -814,7 +828,12 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle } } - this.frm.add_fetch("subcontracting_order", "supplier", "supplier"); + if (me.frm.doc.purchase_order) { + this.frm.add_fetch("purchase_order", "supplier", "supplier"); + } + else { + this.frm.add_fetch("subcontracting_order", "supplier", "supplier"); + } frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'supplier', doctype: 'Supplier' } this.frm.set_query("supplier_address", erpnext.queries.address_query) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 86f1b6a486..abe98e2933 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -148,11 +148,11 @@ "search_index": 1 }, { + "depends_on": "eval:doc.purpose==\"Send to Subcontractor\"", "fieldname": "purchase_order", "fieldtype": "Link", "label": "Purchase Order", - "options": "Purchase Order", - "read_only": 1 + "options": "Purchase Order" }, { "depends_on": "eval:doc.purpose==\"Send to Subcontractor\"", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 6599eddd4a..d3f15e703f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -62,6 +62,27 @@ form_grid_templates = {"items": "templates/form_grid/stock_entry_grid.html"} class StockEntry(StockController): + def __init__(self, *args, **kwargs): + super(StockEntry, self).__init__(*args, **kwargs) + if self.purchase_order: + self.subcontract_data = frappe._dict( + { + "order_doctype": "Purchase Order", + "order_field": "purchase_order", + "rm_detail_field": "po_detail", + "order_supplied_items_field": "Purchase Order Item Supplied", + } + ) + else: + self.subcontract_data = frappe._dict( + { + "order_doctype": "Subcontracting Order", + "order_field": "subcontracting_order", + "rm_detail_field": "sco_rm_detail", + "order_supplied_items_field": "Subcontracting Order Supplied Item", + } + ) + def get_feed(self): return self.stock_entry_type @@ -134,8 +155,8 @@ class StockEntry(StockController): update_serial_nos_after_submit(self, "items") self.update_work_order() - self.validate_subcontracting_order() - self.update_subcontracting_order_supplied_items() + self.validate_subcontract_order() + self.update_subcontract_order_supplied_items() self.update_subcontracting_order_status() self.make_gl_entries() @@ -155,7 +176,7 @@ class StockEntry(StockController): self.set_material_request_transfer_status("Completed") def on_cancel(self): - self.update_subcontracting_order_supplied_items() + self.update_subcontract_order_supplied_items() self.update_subcontracting_order_status() if self.work_order and self.purpose == "Material Consumption for Manufacture": @@ -809,8 +830,8 @@ class StockEntry(StockController): serial_nos.append(sn) - def validate_subcontracting_order(self): - """Throw exception if more raw material is transferred against Subcontracting Order than in + def validate_subcontract_order(self): + """Throw exception if more raw material is transferred against Subcontract Order than in the raw materials supplied table""" backflush_raw_materials_based_on = frappe.db.get_single_value( "Buying Settings", "backflush_raw_materials_of_subcontract_based_on" @@ -818,28 +839,29 @@ class StockEntry(StockController): qty_allowance = flt(frappe.db.get_single_value("Buying Settings", "over_transfer_allowance")) - if not (self.purpose == "Send to Subcontractor" and self.subcontracting_order): + if not (self.purpose == "Send to Subcontractor" and self.get(self.subcontract_data.order_field)): return if backflush_raw_materials_based_on == "BOM": - subcontracting_order = frappe.get_doc("Subcontracting Order", self.subcontracting_order) + subcontract_order = frappe.get_doc( + self.subcontract_data.order_doctype, self.get(self.subcontract_data.order_field) + ) for se_item in self.items: item_code = se_item.original_item or se_item.item_code precision = cint(frappe.db.get_default("float_precision")) or 3 required_qty = sum( - [ - flt(d.required_qty) - for d in subcontracting_order.supplied_items - if d.rm_item_code == item_code - ] + [flt(d.required_qty) for d in subcontract_order.supplied_items if d.rm_item_code == item_code] ) total_allowed = required_qty + (required_qty * (qty_allowance / 100)) if not required_qty: bom_no = frappe.db.get_value( - "Subcontracting Order Item", - {"parent": self.subcontracting_order, "item_code": se_item.subcontracted_item}, + f"{self.subcontract_data.order_doctype} Item", + { + "parent": self.get(self.subcontract_data.order_field), + "item_code": se_item.subcontracted_item, + }, "bom", ) @@ -851,7 +873,7 @@ class StockEntry(StockController): required_qty = sum( [ flt(d.required_qty) - for d in subcontracting_order.supplied_items + for d in subcontract_order.supplied_items if d.rm_item_code == original_item_code ] ) @@ -860,43 +882,57 @@ class StockEntry(StockController): if not required_qty: frappe.throw( - _("Item {0} not found in 'Raw Materials Supplied' table in Subcontracting Order {1}").format( - se_item.item_code, self.subcontracting_order + _("Item {0} not found in 'Raw Materials Supplied' table in {1} {2}").format( + se_item.item_code, + self.subcontract_data.order_doctype, + self.get(self.subcontract_data.order_field), ) ) parent = frappe.qb.DocType("Stock Entry") child = frappe.qb.DocType("Stock Entry Detail") + conditions = ( + (parent.docstatus == 1) + & (child.item_code == se_item.item_code) + & ( + (parent.purchase_order == self.purchase_order) + if self.subcontract_data.order_doctype == "Purchase Order" + else (parent.subcontracting_order == self.subcontracting_order) + ) + ) + total_supplied = ( frappe.qb.from_(parent) .inner_join(child) .on(parent.name == child.parent) .select(Sum(child.transfer_qty)) - .where(parent.docstatus == 1) - .where(parent.subcontracting_order == self.subcontracting_order) - .where(child.item_code == se_item.item_code) + .where(conditions) ).run()[0][0] if flt(total_supplied, precision) > flt(total_allowed, precision): frappe.throw( - _( - "Row {0}# Item {1} cannot be transferred more than {2} against Subcontracting Order {3}" - ).format( - se_item.idx, se_item.item_code, total_allowed, self.subcontracting_order + _("Row {0}# Item {1} cannot be transferred more than {2} against {3} {4}").format( + se_item.idx, + se_item.item_code, + total_allowed, + self.subcontract_data.order_doctype, + self.get(self.subcontract_data.order_field), ) ) - elif not se_item.sco_rm_detail: + elif not se_item.get(self.subcontract_data.rm_detail_field): filters = { - "parent": self.subcontracting_order, + "parent": self.get(self.subcontract_data.order_field), "docstatus": 1, "rm_item_code": se_item.item_code, "main_item_code": se_item.subcontracted_item, } - sco_rm_detail = frappe.db.get_value("Subcontracting Order Supplied Item", filters, "name") - if sco_rm_detail: - se_item.db_set("sco_rm_detail", sco_rm_detail) + order_rm_detail = frappe.db.get_value( + self.subcontract_data.order_supplied_items_field, filters, "name" + ) + if order_rm_detail: + se_item.db_set(self.subcontract_data.rm_detail_field, order_rm_detail) elif backflush_raw_materials_based_on == "Material Transferred for Subcontract": for row in self.items: if not row.subcontracted_item: @@ -905,17 +941,19 @@ class StockEntry(StockController): row.idx, frappe.bold(row.item_code) ) ) - elif not row.sco_rm_detail: + elif not row.get(self.subcontract_data.rm_detail_field): filters = { - "parent": self.subcontracting_order, + "parent": self.get(self.subcontract_data.order_field), "docstatus": 1, "rm_item_code": row.item_code, "main_item_code": row.subcontracted_item, } - sco_rm_detail = frappe.db.get_value("Subcontracting Order Supplied Item", filters, "name") - if sco_rm_detail: - row.db_set("sco_rm_detail", sco_rm_detail) + order_rm_detail = frappe.db.get_value( + self.subcontract_data.order_supplied_items_field, filters, "name" + ) + if order_rm_detail: + row.db_set(self.subcontract_data.rm_detail_field, order_rm_detail) def validate_bom(self): for d in self.get("items"): @@ -1263,12 +1301,12 @@ class StockEntry(StockController): if ( self.purpose == "Send to Subcontractor" - and self.get("subcontracting_order") + and self.get(self.subcontract_data.order_field) and args.get("item_code") ): subcontract_items = frappe.get_all( - "Subcontracting Order Supplied Item", - {"parent": self.subcontracting_order, "rm_item_code": args.get("item_code")}, + self.subcontract_data.order_supplied_items_field, + {"parent": self.get(self.subcontract_data.order_field), "rm_item_code": args.get("item_code")}, "main_item_code", ) @@ -1362,18 +1400,18 @@ class StockEntry(StockController): item_dict = self.get_bom_raw_materials(self.fg_completed_qty) - # Get SCO Supplied Items Details - if self.subcontracting_order and self.purpose == "Send to Subcontractor": - # Get SCO Supplied Items Details - parent = frappe.qb.DocType("Subcontracting Order") - child = frappe.qb.DocType("Subcontracting Order Supplied Item") + # Get Subcontract Order Supplied Items Details + if self.get(self.subcontract_data.order_field) and self.purpose == "Send to Subcontractor": + # Get Subcontract Order Supplied Items Details + parent = frappe.qb.DocType(self.subcontract_data.order_doctype) + child = frappe.qb.DocType(self.subcontract_data.order_supplied_items_field) item_wh = ( frappe.qb.from_(parent) .inner_join(child) .on(parent.name == child.parent) .select(child.rm_item_code, child.reserve_warehouse) - .where(parent.name == self.subcontracting_order) + .where(parent.name == self.get(self.subcontract_data.order_field)) ).run(as_list=True) item_wh = frappe._dict(item_wh) @@ -1381,8 +1419,8 @@ class StockEntry(StockController): for item in item_dict.values(): if self.pro_doc and cint(self.pro_doc.from_wip_warehouse): item["from_warehouse"] = self.pro_doc.wip_warehouse - # Get Reserve Warehouse from SCO - if self.subcontracting_order and self.purpose == "Send to Subcontractor": + # Get Reserve Warehouse from Subcontract Order + if self.get(self.subcontract_data.order_field) and self.purpose == "Send to Subcontractor": item["from_warehouse"] = item_wh.get(item.item_code) item["to_warehouse"] = self.to_warehouse if self.purpose == "Send to Subcontractor" else "" @@ -1519,7 +1557,9 @@ class StockEntry(StockController): fetch_qty_in_stock_uom=False, ) - used_alternative_items = get_used_alternative_items(work_order=self.work_order) + used_alternative_items = get_used_alternative_items( + subcontract_order_field=self.subcontract_data.order_field, work_order=self.work_order + ) for item in item_dict.values(): # if source warehouse presents in BOM set from_warehouse as bom source_warehouse if item["allow_alternative_item"]: @@ -1925,7 +1965,7 @@ class StockEntry(StockController): se_child.is_process_loss = item_row.get("is_process_loss", 0) for field in [ - "sco_rm_detail", + self.subcontract_data.rm_detail_field, "original_item", "expense_account", "description", @@ -1999,33 +2039,37 @@ class StockEntry(StockController): else: frappe.throw(_("Batch {0} of Item {1} is disabled.").format(item.batch_no, item.item_code)) - def update_subcontracting_order_supplied_items(self): - if self.subcontracting_order and ( - self.purpose in ["Send to Subcontractor", "Material Transfer"] + def update_subcontract_order_supplied_items(self): + if self.get(self.subcontract_data.order_field) and ( + self.purpose in ["Send to Subcontractor", "Material Transfer"] or self.is_return ): - # Get SCO Supplied Items Details - sco_supplied_items = frappe.db.get_all( - "Subcontracting Order Supplied Item", - filters={"parent": self.subcontracting_order}, + # Get Subcontract Order Supplied Items Details + order_supplied_items = frappe.db.get_all( + self.subcontract_data.order_supplied_items_field, + filters={"parent": self.get(self.subcontract_data.order_field)}, fields=["name", "rm_item_code", "reserve_warehouse"], ) - # Get Items Supplied in Stock Entries against SCO - supplied_items = get_supplied_items(self.subcontracting_order) + # Get Items Supplied in Stock Entries against Subcontract Order + supplied_items = get_supplied_items( + self.get(self.subcontract_data.order_field), + self.subcontract_data.rm_detail_field, + self.subcontract_data.order_field, + ) - for row in sco_supplied_items: + for row in order_supplied_items: key, item = row.name, {} if not supplied_items.get(key): - # no stock transferred against SCO Supplied Items row + # no stock transferred against Subcontract Order Supplied Items row item = {"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0} else: item = supplied_items.get(key) - frappe.db.set_value("Subcontracting Order Supplied Item", row.name, item) + frappe.db.set_value(self.subcontract_data.order_supplied_items_field, row.name, item) # RM Item-Reserve Warehouse Dict - item_wh = {x.get("rm_item_code"): x.get("reserve_warehouse") for x in sco_supplied_items} + item_wh = {x.get("rm_item_code"): x.get("reserve_warehouse") for x in order_supplied_items} for d in self.get("items"): # Update reserved sub contracted quantity in bin based on Supplied Item Details and @@ -2382,13 +2426,13 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None): return operating_cost_per_unit -def get_used_alternative_items(subcontracting_order=None, work_order=None): +def get_used_alternative_items( + subcontract_order=None, subcontract_order_field="subcontracting_order", work_order=None +): cond = "" - if subcontracting_order: - cond = "and ste.purpose = 'Send to Subcontractor' and ste.subcontracting_order = '{0}'".format( - subcontracting_order - ) + if subcontract_order: + cond = f"and ste.purpose = 'Send to Subcontractor' and ste.{subcontract_order_field} = '{subcontract_order}'" elif work_order: cond = "and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{0}'".format( work_order @@ -2524,25 +2568,27 @@ def validate_sample_quantity(item_code, sample_quantity, qty, batch_no=None): return sample_quantity -def get_supplied_items(subcontracting_order): +def get_supplied_items( + subcontract_order, rm_detail_field="sco_rm_detail", subcontract_order_field="subcontracting_order" +): fields = [ "`tabStock Entry Detail`.`transfer_qty`", "`tabStock Entry`.`is_return`", - "`tabStock Entry Detail`.`sco_rm_detail`", + f"`tabStock Entry Detail`.`{rm_detail_field}`", "`tabStock Entry Detail`.`item_code`", ] filters = [ ["Stock Entry", "docstatus", "=", 1], - ["Stock Entry", "subcontracting_order", "=", subcontracting_order], + ["Stock Entry", subcontract_order_field, "=", subcontract_order], ] supplied_item_details = {} for row in frappe.get_all("Stock Entry", fields=fields, filters=filters): - if not row.sco_rm_detail: + if not row.get(rm_detail_field): continue - key = row.sco_rm_detail + key = row.get(rm_detail_field) if key not in supplied_item_details: supplied_item_details.setdefault( key, frappe._dict({"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0}) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e701c14aa9..c23548c2d0 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -240,8 +240,13 @@ def validate_item_details(args, item): throw(_("Item {0} is a template, please select one of its variants").format(item.name)) elif args.transaction_type == "buying" and args.doctype != "Material Request": - if args.get("is_subcontracted") and item.is_stock_item: - throw(_("Item {0} must be a Non-Stock Item").format(item.name)) + if args.get("is_subcontracted"): + if args.get("is_old_subcontracting_flow"): + if item.is_sub_contracted_item != 1: + throw(_("Item {0} must be a Sub-contracted Item").format(item.name)) + else: + if item.is_stock_item: + throw(_("Item {0} must be a Non-Stock Item").format(item.name)) def get_basic_details(args, item, overwrite_warehouse=True): diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9293dde77f..8d82c7316a 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -738,6 +738,13 @@ class update_entries_after(object): "Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate ) + # Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice + if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"): + doc = frappe.get_doc(sle.voucher_type, sle.voucher_no) + doc.update_valuation_rate(reset_outgoing_rate=False) + for d in doc.items + doc.supplied_items: + d.db_update() + def update_rate_on_subcontracting_receipt(self, sle, outgoing_rate): if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no): frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "rate", outgoing_rate) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index c9e4577cea..dbd337afd4 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -24,7 +24,8 @@ frappe.ui.form.on('Subcontracting Order', { return { filters: { docstatus: 1, - is_subcontracted: 1 + is_subcontracted: 1, + is_old_subcontracting_flow: 0 } }; }); @@ -115,10 +116,14 @@ frappe.ui.form.on('Subcontracting Order', { if (sco_rm_details && sco_rm_details.length) { frm.add_custom_button(__('Return of Components'), () => { frm.call({ - method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.get_materials_from_supplier', + method: 'erpnext.controllers.subcontracting_controller.get_materials_from_supplier', freeze: true, freeze_message: __('Creating Stock Entry'), - args: { subcontracting_order: frm.doc.name, sco_rm_details: sco_rm_details }, + args: { + subcontract_order: frm.doc.name, + rm_details: sco_rm_details, + order_doctype: cur_frm.doc.doctype + }, callback: function (r) { if (r && r.message) { const doc = frappe.model.sync(r.message); @@ -306,10 +311,11 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll make_rm_stock_entry(rm_items) { frappe.call({ - method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_rm_stock_entry', + method: 'erpnext.controllers.subcontracting_controller.make_rm_stock_entry', args: { - subcontracting_order: cur_frm.doc.name, - rm_items: rm_items + subcontract_order: cur_frm.doc.name, + rm_items: rm_items, + order_doctype: cur_frm.doc.doctype }, callback: (r) => { var doclist = frappe.model.sync(r.message); diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index d12c9e825c..3655910efb 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -1,8 +1,6 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -import json - import frappe from frappe import _ from frappe.model.mapper import get_mapped_doc @@ -42,6 +40,9 @@ class SubcontractingOrder(SubcontractingController): if not po.is_subcontracted: frappe.throw(_("Please select a valid Purchase Order that is configured for Subcontracting.")) + if po.is_old_subcontracting_flow: + frappe.throw(_("Please select a valid Purchase Order that has Service Items.")) + if po.docstatus != 1: msg = f"Please submit Purchase Order {po.name} before proceeding." frappe.throw(_(msg)) @@ -227,143 +228,6 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None): return target_doc -def get_item_details(items): - item = frappe.qb.DocType("Item") - item_list = ( - frappe.qb.from_(item) - .select(item.item_code, item.description, item.allow_alternative_item) - .where(item.name.isin(items)) - .run(as_dict=True) - ) - - item_details = {} - for item in item_list: - item_details[item.item_code] = item - - return item_details - - -@frappe.whitelist() -def make_rm_stock_entry(subcontracting_order, rm_items): - rm_items_list = rm_items - - if isinstance(rm_items, str): - rm_items_list = json.loads(rm_items) - elif not rm_items: - frappe.throw(_("No Items available for transfer")) - - if rm_items_list: - fg_items = list(set(item["item_code"] for item in rm_items_list)) - else: - frappe.throw(_("No Items selected for transfer")) - - if subcontracting_order: - subcontracting_order = frappe.get_doc("Subcontracting Order", subcontracting_order) - - if fg_items: - items = tuple(set(item["rm_item_code"] for item in rm_items_list)) - item_wh = get_item_details(items) - - stock_entry = frappe.new_doc("Stock Entry") - stock_entry.purpose = "Send to Subcontractor" - stock_entry.subcontracting_order = subcontracting_order.name - stock_entry.supplier = subcontracting_order.supplier - stock_entry.supplier_name = subcontracting_order.supplier_name - stock_entry.supplier_address = subcontracting_order.supplier_address - stock_entry.address_display = subcontracting_order.address_display - stock_entry.company = subcontracting_order.company - stock_entry.to_warehouse = subcontracting_order.supplier_warehouse - stock_entry.set_stock_entry_type() - - for item_code in fg_items: - for rm_item_data in rm_items_list: - if rm_item_data["item_code"] == item_code: - rm_item_code = rm_item_data["rm_item_code"] - items_dict = { - rm_item_code: { - "sco_rm_detail": rm_item_data.get("name"), - "item_name": rm_item_data["item_name"], - "description": item_wh.get(rm_item_code, {}).get("description", ""), - "qty": rm_item_data["qty"], - "from_warehouse": rm_item_data["warehouse"], - "stock_uom": rm_item_data["stock_uom"], - "serial_no": rm_item_data.get("serial_no"), - "batch_no": rm_item_data.get("batch_no"), - "main_item_code": rm_item_data["item_code"], - "allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"), - } - } - stock_entry.add_to_stock_entry_detail(items_dict) - return stock_entry.as_dict() - else: - frappe.throw(_("No Items selected for transfer")) - return subcontracting_order.name - - -def add_items_in_ste(ste_doc, row, qty, sco_rm_details, batch_no=None): - item = ste_doc.append("items", row.item_details) - - sco_rm_detail = list(set(row.sco_rm_details).intersection(sco_rm_details)) - item.update( - { - "qty": qty, - "batch_no": batch_no, - "basic_rate": row.item_details["rate"], - "sco_rm_detail": sco_rm_detail[0] if sco_rm_detail else "", - "s_warehouse": row.item_details["t_warehouse"], - "t_warehouse": row.item_details["s_warehouse"], - "item_code": row.item_details["rm_item_code"], - "subcontracted_item": row.item_details["main_item_code"], - "serial_no": "\n".join(row.serial_no) if row.serial_no else "", - } - ) - - -def make_return_stock_entry_for_subcontract(available_materials, sco_doc, sco_rm_details): - ste_doc = frappe.new_doc("Stock Entry") - ste_doc.purpose = "Material Transfer" - - ste_doc.subcontracting_order = sco_doc.name - ste_doc.company = sco_doc.company - ste_doc.is_return = 1 - - for key, value in available_materials.items(): - if not value.qty: - continue - - if value.batch_no: - for batch_no, qty in value.batch_no.items(): - if qty > 0: - add_items_in_ste(ste_doc, value, value.qty, sco_rm_details, batch_no) - else: - add_items_in_ste(ste_doc, value, value.qty, sco_rm_details) - - ste_doc.set_stock_entry_type() - ste_doc.calculate_rate_and_amount() - - return ste_doc - - -@frappe.whitelist() -def get_materials_from_supplier(subcontracting_order, sco_rm_details): - if isinstance(sco_rm_details, str): - sco_rm_details = json.loads(sco_rm_details) - - doc = frappe.get_cached_doc("Subcontracting Order", subcontracting_order) - doc.initialized_fields() - doc.subcontracting_orders = [doc.name] - doc.get_available_materials() - - if not doc.available_materials: - frappe.throw( - _("Materials are already received against the Subcontracting Order {0}").format( - subcontracting_order - ) - ) - - return make_return_stock_entry_for_subcontract(doc.available_materials, doc, sco_rm_details) - - @frappe.whitelist() def update_subcontracting_order_status(sco): if isinstance(sco, str): diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index 1454f1aced..e579834963 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -7,6 +7,7 @@ import frappe from frappe.tests.utils import FrappeTestCase from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_subcontracting_order +from erpnext.controllers.subcontracting_controller import make_rm_stock_entry from erpnext.controllers.tests.test_subcontracting_controller import ( get_rm_items, get_subcontracting_order, @@ -22,7 +23,6 @@ from erpnext.controllers.tests.test_subcontracting_controller import ( from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( - make_rm_stock_entry, make_subcontracting_receipt, ) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 5ee49d8502..0c4ec6fb76 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -3,7 +3,7 @@ import frappe from frappe import _ -from frappe.utils import cint, flt, getdate, nowdate +from frappe.utils import cint, getdate, nowdate from erpnext.controllers.subcontracting_controller import SubcontractingController @@ -78,7 +78,7 @@ class SubcontractingReceipt(SubcontractingController): self.update_status_updater_args() self.update_prevdoc_status() self.set_subcontracting_order_status() - self.set_consumed_qty_in_sco() + self.set_consumed_qty_in_subcontract_order() self.update_stock_ledger() from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit @@ -97,7 +97,7 @@ class SubcontractingReceipt(SubcontractingController): self.make_gl_entries_on_cancel() self.repost_future_sle_and_gle() self.delete_auto_created_batches() - self.set_consumed_qty_in_sco() + self.set_consumed_qty_in_subcontract_order() self.set_subcontracting_order_status() self.update_status() @@ -162,17 +162,6 @@ class SubcontractingReceipt(SubcontractingController): if not item.expense_account: item.expense_account = expense_account - @frappe.whitelist() - def get_current_stock(self): - for item in self.get("supplied_items"): - if self.supplier_warehouse: - actual_qty = frappe.db.get_value( - "Bin", - {"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse}, - "actual_qty", - ) - item.current_stock = flt(actual_qty) or 0 - def update_status(self, status=None, update_modified=False): if self.docstatus >= 1 and not status: if self.docstatus == 1: diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 156a2711fa..763e76882e 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -119,7 +119,7 @@ class TestSubcontractingReceipt(FrappeTestCase): receive more than the required qty in the SCO. Expected Result: Error Raised for Over Receipt against SCO. """ - from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + from erpnext.controllers.subcontracting_controller import ( make_rm_stock_entry as make_subcontract_transfer_entry, ) from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( @@ -188,8 +188,8 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertRaises(frappe.ValidationError, scr2.submit) def test_subcontracted_scr_for_multi_transfer_batches(self): + from erpnext.controllers.subcontracting_controller import make_rm_stock_entry from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( - make_rm_stock_entry, make_subcontracting_receipt, ) From f59a6248bcf57a7159f75c350f1d9b73518a6e73 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Fri, 24 Jun 2022 01:02:41 +0300 Subject: [PATCH 54/98] Update ru.csv - fix logic - add translate --- erpnext/translations/ru.csv | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index a4bfb86c01..2dc510cb55 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -563,7 +563,7 @@ Commercial,Коммерческий сектор, Commission,Комиссионный сбор, Commission Rate %,Ставка комиссии %, Commission on Sales,Комиссия по продажам, -Commission rate cannot be greater than 100,"Скорость Комиссия не может быть больше, чем 100", +Commission rate cannot be greater than 100,"Стоимость комиссии не может быть больше, чем 100", Community Forum,Форум, Company (not Customer or Supplier) master.,Компания (не клиента или поставщика) хозяин., Company Abbreviation,Аббревиатура компании, @@ -3187,7 +3187,7 @@ Update Print Format,Обновить формат печати, Update Response,Обновить ответ, Update bank payment dates with journals.,Обновление банк платежные даты с журналов., Update in progress. It might take a while.,Идет обновление. Это может занять некоторое время., -Update rate as per last purchase,Скорость обновления согласно последней покупке, +Update rate as per last purchase,Обновлять стоимость согласно последней покупке, Update stock must be enable for the purchase invoice {0},Обновление запасов должно быть включено для счета на покупку {0}, Updating Variants...,Обновление вариантов..., Upload your letter head and logo. (you can edit them later).,Загрузить шапку фирменного бланка и логотип. (Вы можете отредактировать их позднее)., @@ -4349,7 +4349,7 @@ Valid Upto date cannot be before Valid From date,Дата начала дейс Valid From date not in Fiscal Year {0},Дата начала действия не в финансовом году {0}, Valid Upto date not in Fiscal Year {0},Действительно до даты не в финансовом году {0}, Group Roll No,Групповой опрос №, -Maintain Same Rate Throughout Sales Cycle,Поддержание же скоростью протяжении цикла продаж, +Maintain Same Rate Throughout Sales Cycle,Поддержание одинаковой ставки на протяжении всего цикла продаж, "Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}.","Строка {1}: количество ({0}) не может быть дробью. Чтобы разрешить это, отключите '{2}' в единицах измерения {3}.", Must be Whole Number,Должно быть целое число, Please setup Razorpay Plan ID,Настройте идентификатор плана Razorpay, @@ -5505,7 +5505,7 @@ Returned Qty,Вернулся Кол-во, Purchase Order Item Supplied,Заказ товара Поставляется, BOM Detail No,Подробности спецификации №, Stock Uom,Единица измерения запасов, -Raw Material Item Code,Код сырьевой позиции, +Raw Material Item Code,Код исходного материала, Supplied Qty,Поставляемое кол-во, Purchase Receipt Item Supplied,Покупка Получение товара Поставляется, Current Stock,Наличие на складе, @@ -5541,7 +5541,7 @@ Default Payable Accounts,По умолчанию задолженность Кр Mention if non-standard payable account,"Упомяните, если нестандартный подлежащий оплате счет", Default Tax Withholding Config,Конфигурация удержания налога по умолчанию, Supplier Details,Подробная информация о поставщике, -Statutory info and other general information about your Supplier,Уставный информации и другие общие сведения о вашем Поставщик, +Statutory info and other general information about your Supplier,Правовая информация и другие общие сведения о вашем поставщике, PUR-SQTN-.YYYY.-,PUR-SQTN-.YYYY.-, Supplier Address,Адрес поставщика, Link to material requests,Ссылка на заявки на материалы, @@ -6198,7 +6198,7 @@ Vacant,Вакантно, Occupied,Занято, Item Details,Детальная информация о продукте, UOM Conversion in Hours,Преобразование UOM в часы, -Rate / UOM,Скорость / UOM, +Rate / UOM,Стоимость / UOM, Change in Item,Изменение продукта, Out Patient Settings,Настройки пациента, Patient Name By,Имя пациента, @@ -6890,7 +6890,7 @@ Employee Loan,Сотрудник займа, Total Principal Amount,Общая сумма, Total Interest Amount,Общая сумма процентов, Total Loan Repayment,Общая сумма погашения кредита, -net pay info,Чистая информация платить, +net pay info,информация о чистой оплате, Gross Pay - Total Deduction - Loan Repayment,Валовая заработная плата - Общий вычет - Погашение кредита, Total in words,Всего в словах, Net Pay (in words) will be visible once you save the Salary Slip.,"Чистая плата (прописью) будет видна, как только вы сохраните зарплатную ведомость.", @@ -6926,9 +6926,9 @@ Last Sync of Checkin,Последняя синхронизация регист Last Known Successful Sync of Employee Checkin. Reset this only if you are sure that all Logs are synced from all the locations. Please don't modify this if you are unsure.,"Последняя известная успешная синхронизация регистрации сотрудника. Сбрасывайте это, только если вы уверены, что все журналы синхронизированы из всех мест. Пожалуйста, не изменяйте это, если вы не уверены.", Grace Period Settings For Auto Attendance,Настройки льготного периода для автоматической посещаемости, Enable Entry Grace Period,Включить льготный период, -Late Entry Grace Period,Льготный период позднего въезда, +Late Entry Grace Period,Льготный период позднего входа, The time after the shift start time when check-in is considered as late (in minutes).,"Время после начала смены, когда регистрация считается поздней (в минутах).", -Enable Exit Grace Period,Включить Exit Grace Period, +Enable Exit Grace Period,Разрешить выход из льготного периода, Early Exit Grace Period,Льготный период раннего выхода, The time before the shift end time when check-out is considered as early (in minutes).,Время до окончания смены при выезде считается ранним (в минутах)., Skill Name,Название навыка, @@ -7184,7 +7184,7 @@ Blanket Order Item,Элемент заказа одеяла, Ordered Quantity,Заказанное количество, Item to be manufactured or repacked,Продукт должен быть произведен или переупакован, Quantity of item obtained after manufacturing / repacking from given quantities of raw materials,Количество пункта получены после изготовления / переупаковка от заданных величин сырья, -Set rate of sub-assembly item based on BOM,Установить скорость сборки на основе спецификации, +Set rate of sub-assembly item based on BOM,Установить стоимость сборки на основе спецификации, Allow Alternative Item,Разрешить альтернативный продукт, Item UOM,Единиц продукта, Conversion Rate,Коэффициент конверсии, @@ -7228,7 +7228,7 @@ BOM Operation,Операция спецификации, Operation Time ,Время операции, In minutes,В считанные минуты, Batch Size,Размер партии, -Base Hour Rate(Company Currency),Базовый час Rate (в валюте компании), +Base Hour Rate(Company Currency),Базовая часовая ставка (в валюте компании), Operating Cost(Company Currency),Эксплуатационные расходы (в валюте компании), BOM Scrap Item,Спецификация отходов продукта, Basic Amount (Company Currency),Базовая сумма (в валюте компании), @@ -8304,7 +8304,7 @@ Default Source Warehouse,По умолчанию склад сырья, Source Warehouse Address,Адрес источника склада, Default Target Warehouse,Цель по умолчанию Склад, Target Warehouse Address,Адрес целевого склада, -Update Rate and Availability,Скорость обновления и доступность, +Update Rate and Availability,Обновить стоимость и доступность, Total Incoming Value,Всего входное значение, Total Outgoing Value,Всего исходящее значение, Total Value Difference (Out - In),Общая стоимость Разница (Out - In), @@ -8888,7 +8888,7 @@ Practitioner Name,Имя практикующего, Enter a name for the Clinical Procedure Template,Введите имя для шаблона клинической процедуры, Set the Item Code which will be used for billing the Clinical Procedure.,"Установите код товара, который будет использоваться для выставления счета за клиническую процедуру.", Select an Item Group for the Clinical Procedure Item.,Выберите группу элементов для элемента клинической процедуры., -Clinical Procedure Rate,Скорость клинической процедуры, +Clinical Procedure Rate,Стоимость клинической процедуры, Check this if the Clinical Procedure is billable and also set the rate.,"Отметьте это, если клиническая процедура оплачивается, а также установите ставку.", Check this if the Clinical Procedure utilises consumables. Click ,"Проверьте это, если в клинической процедуре используются расходные материалы. Нажмите", to know more,узнать больше, @@ -9109,7 +9109,7 @@ MAT-PR-RET-.YYYY.-,MAT-PR-RET-.YYYY.-, Track this Purchase Receipt against any Project,Отслеживайте эту квитанцию о покупке для любого проекта, Please Select a Supplier,"Пожалуйста, выберите поставщика", Add to Transit,Добавить в общественный транспорт, -Set Basic Rate Manually,Установить базовую скорость вручную, +Set Basic Rate Manually,Установить базовую стоимость вручную, "By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a ","По умолчанию имя элемента устанавливается в соответствии с введенным кодом элемента. Если вы хотите, чтобы элементы назывались", Set a Default Warehouse for Inventory Transactions. This will be fetched into the Default Warehouse in the Item master.,Установите склад по умолчанию для складских операций. Он будет загружен в Хранилище по умолчанию в мастере предметов., "This will allow stock items to be displayed in negative values. Using this option depends on your use case. With this option unchecked, the system warns before obstructing a transaction that is causing negative stock.","Это позволит отображать товары на складе с отрицательными значениями. Использование этой опции зависит от вашего варианта использования. Если этот параметр не отмечен, система предупреждает, прежде чем препятствовать транзакции, вызывающей отрицательный запас.", @@ -9845,3 +9845,4 @@ Overdue,Просрочено, Completed,Завершенно, Total Tasks,Всего задач, Build,Конструктор, +Amend,Исправить, From 2826056d41c164a03d1b945b4110b626ad9d2418 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Fri, 24 Jun 2022 11:14:57 +0300 Subject: [PATCH 55/98] Update ru.csv --- erpnext/translations/ru.csv | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 2dc510cb55..d72b91f947 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -1066,7 +1066,7 @@ For Employee,Для сотрудника, For Quantity (Manufactured Qty) is mandatory,Для Количество (Изготовитель Количество) является обязательным, For Supplier,Для поставщиков, For Warehouse,Для склада, -For Warehouse is required before Submit,Для Склада является обязательным полем для проведения, +For Warehouse is required before Submit,Для склада - обязательное полем для проводки, "For an item {0}, quantity must be negative number",Для элемента {0} количество должно быть отрицательным числом, "For an item {0}, quantity must be positive number",Для элемента {0} количество должно быть положительным числом, "For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry",Для карты задания {0} вы можете только сделать запись запаса типа 'Передача материала для производства', @@ -1498,7 +1498,7 @@ Maintenance Schedule,График технического обслуживан Maintenance Schedule is not generated for all the items. Please click on 'Generate Schedule',"График обслуживания не генерируется для всех элементов. Пожалуйста, нажмите на кнопку ""Generate Расписание""", Maintenance Schedule {0} exists against {1},График обслуживания {0} существует против {1}, Maintenance Schedule {0} must be cancelled before cancelling this Sales Order,График Обслуживания {0} должен быть отменен до отмены этой Сделки, -Maintenance Status has to be Cancelled or Completed to Submit,Статус обслуживания должен быть отменен или завершен для отправки, +Maintenance Status has to be Cancelled or Completed to Submit,Статус обслуживания должен быть отменен или завершен для проводки, Maintenance User,Сотрудник обслуживания, Maintenance Visit,Заявки на техническое обслуживание, Maintenance Visit {0} must be cancelled before cancelling this Sales Order,Посещение по Обслуживанию {0} должно быть отменено до отмены этой Сделки, @@ -1683,7 +1683,7 @@ No Data,Нет данных, No Delivery Note selected for Customer {},Нет примечания о доставке для клиента {}, No Employee Found,Сотрудник не найден, No Item with Barcode {0},Нет продукта со штрих-кодом {0}, -No Item with Serial No {0},Нет продукта с серийным № {0}, +No Item with Serial No {0},Нет продукта с серийным номером {0}, No Items available for transfer,Нет доступных продуктов для перемещения, No Items selected for transfer,Не выбраны продукты для перемещения, No Items to pack,Нет продуктов для упаковки, @@ -2845,11 +2845,11 @@ Sub Type,Подтип, Sub-contracting,Суб-контракты, Subcontract,Субподряд, Subject,Тема, -Submit,Провести, +Submit,Утвердить, Submit Proof,Отправить подтверждение, -Submit Salary Slip,Провести Зарплатную ведомость, -Submit this Work Order for further processing.,Отправьте этот рабочий заказ для дальнейшей обработки., -Submit this to create the Employee record,"Отправьте это, чтобы создать запись сотрудника", +Submit Salary Slip,Утведрдить зарплатную ведомость, +Submit this Work Order for further processing.,Утвердите этот рабочий заказ для дальнейшей обработки., +Submit this to create the Employee record,"Утвердите это, чтобы создать запись сотрудника", Submitting Salary Slips...,Отправка зарплатных листов ..., Subscription,Подписка, Subscription Management,Управление подпиской, @@ -3310,7 +3310,7 @@ Work Order {0} must be cancelled before cancelling this Sales Order,Заказ Work Order {0} must be submitted,Порядок работы {0} должен быть отправлен, Work Orders Created: {0},Созданы рабочие задания: {0}, Work Summary for {0},Резюме работы для {0}, -Work-in-Progress Warehouse is required before Submit,Работа-в-Прогресс Склад требуется перед Отправить, +Work-in-Progress Warehouse is required before Submit,Перед утверждением требуется склад незавершенного производства, Workflow,Рабочий процесс, Working,В работе, Working Hours,Часы работы, @@ -4072,7 +4072,7 @@ Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and Stores - {0},Магазины - {0}, Student with email {0} does not exist,Студент с электронной почтой {0} не существует, Submit Review,Добавить отзыв, -Submitted,Проведенный, +Submitted,Утвержден, Supplier Addresses And Contacts,Адреса и контакты поставщика, Synchronize this account,Синхронизировать этот аккаунт, Tag,Тег, @@ -6266,7 +6266,7 @@ Discharge Date,Дата выписки, Lab Prescription,Лабораторный рецепт, Lab Test Name,Название лабораторного теста, Test Created,Тест создан, -Submitted Date,Дата отправки, +Submitted Date,Дата утверждения, Approved Date,Утвержденная дата, Sample ID,Образец, Lab Technician,Лаборант, @@ -9067,7 +9067,7 @@ Rented To Date,Сдано на дату, Monthly Eligible Amount,Ежемесячная приемлемая сумма, Total Eligible HRA Exemption,Полное соответствие требованиям HRA, Validating Employee Attendance...,Проверка явки сотрудников..., -Submitting Salary Slips and creating Journal Entry...,Отправка ведомостей о заработной плате и создание записи в журнале ..., +Submitting Salary Slips and creating Journal Entry...,Утверждение ведомостей о заработной плате и создание записи в журнале ..., Calculate Payroll Working Days Based On,Расчет рабочих дней для расчета заработной платы на основе, Consider Unmarked Attendance As,Считайте неотмеченную посещаемость как, Fraction of Daily Salary for Half Day,Доля дневной заработной платы за полдня, From 913b3939695a2f2ac82fd444c5c3bd40bf4c25ca Mon Sep 17 00:00:00 2001 From: Vladislav Date: Fri, 24 Jun 2022 11:16:39 +0300 Subject: [PATCH 56/98] Update ru.csv --- erpnext/translations/ru.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index d72b91f947..1a5f118ad3 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -2850,7 +2850,7 @@ Submit Proof,Отправить подтверждение, Submit Salary Slip,Утведрдить зарплатную ведомость, Submit this Work Order for further processing.,Утвердите этот рабочий заказ для дальнейшей обработки., Submit this to create the Employee record,"Утвердите это, чтобы создать запись сотрудника", -Submitting Salary Slips...,Отправка зарплатных листов ..., +Submitting Salary Slips...,Проводка зарплатных ведомостей..., Subscription,Подписка, Subscription Management,Управление подпиской, Subscriptions,Подписки, From 49aa7634a301bb13d5ca1e9a2fa24b86707955e3 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Sat, 25 Jun 2022 01:32:24 +0300 Subject: [PATCH 57/98] Update ru.csv --- erpnext/translations/ru.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 1a5f118ad3..57c4f97486 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -408,7 +408,7 @@ Boms,Boms, Bonus Payment Date cannot be a past date,Дата выплаты бонуса не может быть прошлой датой, Both Trial Period Start Date and Trial Period End Date must be set,"Должны быть установлены как дата начала пробного периода, так и дата окончания пробного периода", Both Warehouse must belong to same Company,Оба Склад должены принадлежать одной Компании, -Branch,Ветвь, +Branch,Филиал, Broadcasting,Вещание, Brokerage,Посредничество, Browse BOM,Просмотр спецификации, From 99504ebe52fd56ad3c1283831c60a94dfbb21cb2 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Sun, 26 Jun 2022 00:15:30 +0300 Subject: [PATCH 58/98] Update ru.csv --- erpnext/translations/ru.csv | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 57c4f97486..ce055c167f 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -2807,8 +2807,8 @@ Stock Transactions,Транзакции запасов, Stock UOM,Единица измерения запасов, Stock Value,Стоимость акций, Stock balance in Batch {0} will become negative {1} for Item {2} at Warehouse {3},Для продукта {2} на складе {3} остатки запасов для партии {0} станут отрицательными {1}, -Stock cannot be updated against Delivery Note {0},Фото не могут быть обновлены против накладной {0}, -Stock cannot be updated against Purchase Receipt {0},Фото не может быть обновлен с квитанцией о покупке {0}, +Stock cannot be updated against Delivery Note {0},Запасы не могут быть обновлены против накладной {0}, +Stock cannot be updated against Purchase Receipt {0},Запасы не может быть обновлен с квитанцией о покупке {0}, Stock cannot exist for Item {0} since has variants,Запасов продукта {0} не существует с момента появления вариантов, Stock transactions before {0} are frozen,Перемещения по складу до {0} заморожены, Stop,Стоп, @@ -8328,7 +8328,7 @@ Stock Ledger Entry,Записи в остатках, Outgoing Rate,Исходящие Оценить, Actual Qty After Transaction,Остаток после проведения, Stock Value Difference,Расхождение стоимости запасов, -Stock Queue (FIFO),Фото со Очередь (FIFO), +Stock Queue (FIFO),Очередь запасов (FIFO), Is Cancelled,Является отмененным, Stock Reconciliation,Инвентаризация запасов, This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.,"Этот инструмент поможет вам обновить или исправить количество и оценку запасов в системе. Это, как правило, используется для синхронизации системных значений и то, что на самом деле существует в ваших складах.", @@ -9839,10 +9839,14 @@ Get Items from Material Requests against this Supplier,Получить това Enable European Access,Включить европейский доступ, Creating Purchase Order ...,Создание заказа на поставку ..., "Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Выберите поставщика из списка поставщиков по умолчанию для позиций ниже. При выборе Заказ на поставку будет сделан в отношении товаров, принадлежащих только выбранному Поставщику.", -Row #{}: You must select {} serial numbers for item {}.,Строка № {}: необходимо выбрать {} серийных номеров для позиции {}., +Row #{}: You must select {} serial numbers for item {}.,Строка №{}: необходимо выбрать {} серийных номеров для позиции {}., Items & Pricing,Продукты и цены, Overdue,Просрочено, Completed,Завершенно, Total Tasks,Всего задач, Build,Конструктор, Amend,Исправить, +Role Allowed to Over Deliver/Receive,"Роль, разрешенная для сверхдоставки/получения", +Unit of Measure (UOM),Единицы измерения (ЕИ), +Bank Reconciliation Tool,Инструмент сверки банковских счетов, +Delayed Tasks Summary,Сводка отложенных задач, From 7c682c55fac569a19186282d2061163d8fea584f Mon Sep 17 00:00:00 2001 From: Vladislav Date: Sun, 26 Jun 2022 01:36:11 +0300 Subject: [PATCH 59/98] Update ru.csv --- erpnext/translations/ru.csv | 62 ++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index ce055c167f..df9d19a5b3 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -3843,7 +3843,7 @@ Mobile No,Мобильный номер, Mobile Number,Мобильный номер, Month,Mесяц, Name,Имя, -Near you,Возле тебя, +Near you,Возле вас, Net Profit/Loss,Чистая прибыль / убыток, New Expense,Новый расход, New Invoice,Новый счет, @@ -3851,8 +3851,8 @@ New Payment,Новый платеж, New release date should be in the future,Дата нового релиза должна быть в будущем, Newsletter,Рассылка новостей, No Account matched these filters: {},"Нет аккаунта, соответствующего этим фильтрам: {}", -No Employee found for the given employee field value. '{}': {},Сотрудник не найден для данного значения поля сотрудника. '{}': {}, -No Leaves Allocated to Employee: {0} for Leave Type: {1},Сотрудникам не выделено ни одного листа: {0} для типа отпуска: {1}, +No Employee found for the given employee field value. '{}': {},Сотрудник не найден для данного значения поля сотрудника. '{}': {}, +No Leaves Allocated to Employee: {0} for Leave Type: {1},Сотруднику не назначен отпуск: {0} для типа отпуска: {1}, No communication found.,Связь не найдена., No correct answer is set for {0},Не указан правильный ответ для {0}, No description,Без описания, @@ -3883,8 +3883,8 @@ Only expired allocation can be cancelled,Отменить можно тольк Only users with the {0} role can create backdated leave applications,Только пользователи с ролью {0} могут создавать оставленные приложения с задним сроком действия, Open,Открыт, Open Contact,Открытый контакт, -Open Lead,Открытое обращение, -Opening and Closing,Открытие и Закрытие, +Open Lead,Открытый лид, +Opening and Closing,Открытие и закрытие, Operating Cost as per Work Order / BOM,Эксплуатационные расходы согласно заказу на работу / спецификации, Order Amount,Сумма заказа, Page {0} of {1},Страница {0} из {1}, @@ -4113,7 +4113,7 @@ Total Early Exits,Всего ранних выходов, Total Late Entries,Всего поздних заявок, Total Payment Request amount cannot be greater than {0} amount,Общая сумма запроса платежа не может превышать сумму {0}, Total payments amount can't be greater than {},Общая сумма платежей не может быть больше {}, -Totals,Всего:, +Totals,Всего, Training Event:,Учебное мероприятие:, Transactions already retreived from the statement,Транзакции уже получены из заявления, Transfer Material to Supplier,Перевести Материал Поставщику, @@ -4235,7 +4235,7 @@ Add to cart,Добавить в корзину, Budget,Бюджет, Chart of Accounts,План счетов, Customer database.,База данных клиентов., -Days Since Last order,Дни с последнего Заказать, +Days Since Last order,Дней с момента последнего заказа, Download as JSON,Скачать как JSON, End date can not be less than start date,"Дата окончания не может быть меньше, чем Дата начала", For Default Supplier (Optional),Поставщик по умолчанию (необязательно), @@ -4253,7 +4253,7 @@ No students found,Нет студентов не найдено, Not in stock,Нет в наличии, Not permitted,Не разрешено, Open Issues ,Открытые вопросы , -Open Projects ,Открытые проекты, +Open Projects ,Открытые проекты , Open To Do ,Открыть список задач , Operation Id,Код операции, Partially ordered,Частично заказанно, @@ -4304,9 +4304,9 @@ Row {}: Asset Naming Series is mandatory for the auto creation for item {},Ст Assets not created for {0}. You will have to create asset manually.,Активы не созданы для {0}. Вам придется создать актив вручную., {0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.,{0} {1} имеет бухгалтерские записи в валюте {2} для компании {3}. Выберите счет дебиторской или кредиторской задолженности с валютой {2}., Invalid Account,Неверный аккаунт, -Purchase Order Required,"Покупка порядке, предусмотренном", -Purchase Receipt Required,Покупка Получение необходимое, -Account Missing,Аккаунт отсутствует, +Purchase Order Required,Требуется заказ на покупку, +Purchase Receipt Required,Требуется чек о покупке, +Account Missing,Счет отсутствует, Requested,Запрошено, Partially Paid,Частично оплачено, Invalid Account Currency,Неверная валюта счета, @@ -4355,9 +4355,9 @@ Must be Whole Number,Должно быть целое число, Please setup Razorpay Plan ID,Настройте идентификатор плана Razorpay, Contact Creation Failed,Не удалось создать контакт, {0} already exists for employee {1} and period {2},{0} уже существует для сотрудника {1} и периода {2}, -Leaves Allocated,Распределенные листья, -Leaves Expired,Листья просрочены, -Leave Without Pay does not match with approved {} records,"Leave Without Pay" не совпадает с утвержденными записями: {}, +Leaves Allocated,Распределенные отпуска, +Leaves Expired,Отпуска просрочены, +Leave Without Pay does not match with approved {} records,Leave Without Pay не совпадает с утвержденными записями: {}, Income Tax Slab not set in Salary Structure Assignment: {0},Плита подоходного налога не указана в назначении структуры заработной платы: {0}, Income Tax Slab: {0} is disabled,Плита подоходного налога: {0} отключена, Income Tax Slab must be effective on or before Payroll Period Start Date: {0},Таблица подоходного налога должна вступить в силу не позднее даты начала периода расчета зарплаты: {0}, @@ -4375,7 +4375,7 @@ Please set Warehouse in Woocommerce Settings,"Пожалуйста, устано Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same,Строка {0}: Delivery Warehouse ({1}) и Customer Warehouse ({2}) не могут совпадать., Row {0}: Due Date in the Payment Terms table cannot be before Posting Date,Строка {0}: Дата платежа в таблице условий оплаты не может быть раньше даты публикации., Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.,Не удается найти {} для элемента {}. Установите то же самое в Мастер предметов или Настройки запасов., -Row #{0}: The batch {1} has already expired.,Строка № {0}: срок действия пакета {1} уже истек., +Row #{0}: The batch {1} has already expired.,Строка №{0}: срок действия пакета {1} уже истек., Start Year and End Year are mandatory,Год начала и год окончания являются обязательными, GL Entry,БК запись, Cannot allocate more than {0} against payment term {1},Невозможно выделить более {0} на срок платежа {1}, @@ -4529,7 +4529,7 @@ Automatically Fetch Payment Terms,Автоматически получать у Show Payment Schedule in Print,Показать график платежей в печати, Currency Exchange Settings,Настройки обмена валюты, Allow Stale Exchange Rates,Разрешить статичные обменные курсы, -Stale Days,Прошлые дни, +Stale Days,Прошедшие дни, Report Settings,Настройки отчета, Use Custom Cash Flow Format,Использовать формат пользовательского денежного потока, Allowed To Transact With,Разрешено спрятать, @@ -4628,16 +4628,16 @@ Action if Accumulated Monthly Budget Exceeded on Actual,"Действие, ес Budget Accounts,Счета бюджета, Budget Account,Бюджет аккаунта, Budget Amount,Сумма бюджета, -C-Form,C-образный, +C-Form,C-Форма, ACC-CF-.YYYY.-,ACC-CF-.YYYY.-, -C-Form No,C-образный Нет, +C-Form No,C-Форма №, Received Date,Дата получения, Quarter,Квартал, I,I, II,II, III,III, IV,IV, -C-Form Invoice Detail,C-образный Счет Подробно, +C-Form Invoice Detail,C-Форма детали счета, Invoice No,Номер cчета, Cash Flow Mapper,Диспетчер денежных потоков, Section Name,Название раздела, @@ -4739,7 +4739,7 @@ GST Account,НДС счет, CGST Account,CGST счет, SGST Account,SGST счет, IGST Account,IGST счет, -CESS Account,CESS-аккаунт, +CESS Account,CESS-счет, Loan Start Date,Дата начала займа, Loan Period (Days),Срок кредитования (дни), Loan End Date,Дата окончания займа, @@ -4806,15 +4806,15 @@ Collection Tier,Уровень сбора, Collection Rules,Правила сбора, Redemption,Выплата, Conversion Factor,Коэффициент конверсии, -1 Loyalty Points = How much base currency?,1 Бонусные баллы = Сколько базовой валюты?, +1 Loyalty Points = How much base currency?,1 балл лояльности = Сколько базовой валюты?, Expiry Duration (in days),Продолжительность действия (в днях), Help Section,Раздел справки, Loyalty Program Help,Помощь в программе лояльности, Loyalty Program Collection,Коллекция программы лояльности, Tier Name,Название уровня, Minimum Total Spent,Минимальные общие затраты, -Collection Factor (=1 LP),Коэффициент сбора (=1 Балл), -For how much spent = 1 Loyalty Point,За сколько потраченных = 1 Балл лояльности, +Collection Factor (=1 LP),Коэффициент сбора (=1 БЛ), +For how much spent = 1 Loyalty Point,За сколько потрачено = 1 Балл лояльности, Mode of Payment Account,Форма оплаты счета, Default Account,По умолчанию учетная запись, Default account will be automatically updated in POS Invoice when this mode is selected.,"Учетная запись по умолчанию будет автоматически обновляться в POS-счете, если выбран этот режим.", @@ -5159,7 +5159,7 @@ Brand Name,Имя бренда, Qty as per Stock UOM,Кол-во в соответствии с ед.измерения запасов, Discount and Margin,Скидка и маржа, Rate With Margin,Оценить с маржой, -Discount (%) on Price List Rate with Margin,Скидка (%) на цену Прейскурант с маржой, +Discount (%) on Price List Rate with Margin,Скидка (%) на цену из прайс-листа с маржой, Rate With Margin (Company Currency),Ставка с маржей (в валюте компании), Delivered By Supplier,Доставлено поставщиком, Deferred Revenue,Отложенный доход, @@ -5507,7 +5507,7 @@ BOM Detail No,Подробности спецификации №, Stock Uom,Единица измерения запасов, Raw Material Item Code,Код исходного материала, Supplied Qty,Поставляемое кол-во, -Purchase Receipt Item Supplied,Покупка Получение товара Поставляется, +Purchase Receipt Item Supplied,Квитанция о покупке предоставлена, Current Stock,Наличие на складе, PUR-RFQ-.YYYY.-,PUR-RFQ-.YYYY.-, For individual supplier,Для индивидуального поставщика, @@ -5569,7 +5569,7 @@ Notify Supplier,Сообщите поставщику, Notify Employee,Уведомить сотрудника, Supplier Scorecard Criteria,Критерии оценки поставщиков, Criteria Name,Название критерия, -Max Score,Макс. Оценка, +Max Score,Макс. оценка, Criteria Formula,Формула критериев, Criteria Weight,Вес критериев, Supplier Scorecard Period,Период оценки поставщика, @@ -5752,7 +5752,7 @@ Examiner,экзаменатор, Examiner Name,Имя экзаменатора, Supervisor,Руководитель, Supervisor Name,Имя супервизора, -Evaluate,оценивать, +Evaluate,Оценивать, Maximum Assessment Score,Максимальный балл оценки, Assessment Plan Criteria,Критерии оценки плана, Maximum Score,Максимальный балл, @@ -6179,7 +6179,7 @@ Basic Details,Основные детали, HLC-PRAC-.YYYY.-,HLC-PRAC-.YYYY.-, Mobile,Мобильный, Phone (R),Телефон (R), -Phone (Office),Телефон(офисный), +Phone (Office),Телефон (офис), Employee and User Details,Сведения о сотруднике и пользователе, Hospital,Больница, Appointments,Назначения, @@ -6475,7 +6475,7 @@ On Duty,На службе, Explanation,Объяснение, Compensatory Leave Request,Компенсационный отпуск, Leave Allocation,Распределение отпусков, -Worked On Holiday,Работал на отдыхе, +Worked On Holiday,Работал на праздниках, Work From Date,Работа с даты, Work End Date,Дата окончания работы, Email Sent To,Email отправлен, @@ -6486,7 +6486,7 @@ Daily Work Summary Group User,Ежедневная рабочая группа, email,Эл. адрес, Parent Department,Родительский отдел, Leave Block List,Оставьте список есть, -Days for which Holidays are blocked for this department.,"Дни, для которых Праздники заблокированные для этого отдела.", +Days for which Holidays are blocked for this department.,"Дни, для которые праздники заблокированные для этого отдела.", Leave Approver,Подтверждение отпусков, Expense Approver,Подтверждающий расходы, Department Approver,Подтверждение департамента, @@ -6805,7 +6805,7 @@ Encashable days,Места для инкаширования, Encashment Amount,Сумма инкассации, Leave Ledger Entry,Выйти из книги, Transaction Name,Название транзакции, -Is Carry Forward,Является ли переносить, +Is Carry Forward,Переносится вперед, Is Expired,Истек, Is Leave Without Pay,Отпуск без содержания, Holiday List for Optional Leave,Список праздников для дополнительного отпуска, From 8e4458e0e625b77bee2639305b84395198e8b055 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 30 Jun 2022 16:47:43 +0530 Subject: [PATCH 60/98] fix: failing test Removed this test case as the new POs will not have the Supplied Items table. --- .../purchase_order/test_purchase_order.py | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 728f749b5d..bd7e4e8d86 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -136,43 +136,6 @@ class TestPurchaseOrder(FrappeTestCase): # ordered qty decreases as ordered qty is 0 (deleted row) self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0 - def test_supplied_items_validations_on_po_update_after_submit(self): - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1, qty=5, rate=100) - item = po.items[0] - - original_supplied_items = {po.name: po.required_qty for po in po.supplied_items} - - # Just update rate - trans_item = [ - { - "item_code": "_Test FG Item", - "rate": 20, - "qty": 5, - "conversion_factor": 1.0, - "docname": item.name, - } - ] - update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name) - po.reload() - - new_supplied_items = {po.name: po.required_qty for po in po.supplied_items} - self.assertEqual(set(original_supplied_items.keys()), set(new_supplied_items.keys())) - - # Update qty to 2x - trans_item[0]["qty"] *= 2 - update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name) - po.reload() - - new_supplied_items = {po.name: po.required_qty for po in po.supplied_items} - self.assertEqual(2 * sum(original_supplied_items.values()), sum(new_supplied_items.values())) - - # Set transfer qty and attempt to update qty, shouldn't be allowed - po.supplied_items[0].supplied_qty = 2 - po.supplied_items[0].db_update() - trans_item[0]["qty"] *= 2 - with self.assertRaises(frappe.ValidationError): - update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name) - def test_update_child(self): mr = make_material_request(qty=10) po = make_purchase_order(mr.name) From b86710bb9a72c9b63d6e2b32d0bdf2c6601aebd0 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 30 Jun 2022 17:28:42 +0530 Subject: [PATCH 61/98] fix(ui): hide "Update Items" button based on subcontracting conditions --- .../doctype/purchase_order/purchase_order.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 2b1e3ce914..582f23e311 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -157,14 +157,17 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e if(!in_list(["Closed", "Delivered"], doc.status)) { if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) { - this.frm.add_custom_button(__('Update Items'), () => { - erpnext.utils.update_child_items({ - frm: this.frm, - child_docname: "items", - child_doctype: "Purchase Order Detail", - cannot_add_row: false, - }) - }); + // Don't add Update Items button if the PO is following the new subcontracting flow. + if (!(this.frm.doc.is_subcontracted && !this.frm.doc.is_old_subcontracting_flow)) { + this.frm.add_custom_button(__('Update Items'), () => { + erpnext.utils.update_child_items({ + frm: this.frm, + child_docname: "items", + child_doctype: "Purchase Order Detail", + cannot_add_row: false, + }) + }); + } } if (this.frm.has_perm("submit")) { if(flt(doc.per_billed, 6) < 100 || flt(doc.per_received, 6) < 100) { From fd162f9b14935f29e0e4fde10063a74329d13e30 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 1 Jul 2022 16:51:19 +0530 Subject: [PATCH 62/98] fix: supplier warehouse in PR --- erpnext/controllers/buying_controller.py | 23 ++++++++----------- .../purchase_receipt/purchase_receipt.js | 4 ++-- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 4db8ccb5b8..97843f748e 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -323,22 +323,19 @@ class BuyingController(SubcontractingController): d.margin_rate_or_amount = 0.0 def validate_for_subcontracting(self): - if self.is_subcontracted: + if self.is_subcontracted and self.get("is_old_subcontracting_flow"): 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)) - if self.get("is_old_subcontracting_flow"): - for item in self.get("items"): - 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)) - - if self.doctype != "Purchase Order": - return - - for row in self.get("supplied_items"): - 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(_(msg)) + for item in self.get("items"): + 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)) + if self.doctype != "Purchase Order": + return + for row in self.get("supplied_items"): + 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(_(msg)) else: for item in self.get("items"): if item.get("bom"): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index e6fcb78f12..312c166f8b 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -198,7 +198,7 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend cur_frm.add_custom_button(__('Reopen'), this.reopen_purchase_receipt, __("Status")) } - this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted); + this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_old_subcontracting_flow); } make_purchase_invoice() { @@ -300,7 +300,7 @@ frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) { erpnext.buying.get_default_bom(frm); } - frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted); + frm.toggle_reqd("supplier_warehouse", frm.doc.is_old_subcontracting_flow); }); frappe.ui.form.on('Purchase Receipt Item', { From caeaa3f94086c03154e7a9ee369ce6a4c80f030a Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Sat, 2 Jul 2022 06:20:09 +0530 Subject: [PATCH 63/98] fix: multiple SCO against a PO --- erpnext/buying/doctype/purchase_order/purchase_order.py | 9 +++++++++ .../doctype/subcontracting_order/subcontracting_order.py | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 6f960a2c65..cd58d25136 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -691,3 +691,12 @@ def get_mapped_subcontracting_order(source_name, target_doc=None): item.warehouse = source_doc.items[idx].warehouse return target_doc + + +@frappe.whitelist() +def is_subcontracting_order_created(po_name) -> bool: + count = frappe.db.count( + "Subcontracting Order", {"purchase_order": po_name, "status": ["not in", ["Draft", "Cancelled"]]} + ) + + return True if count else False diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 3655910efb..73ab43401b 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -6,6 +6,7 @@ from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.utils import flt +from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created from erpnext.controllers.subcontracting_controller import SubcontractingController from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty from erpnext.stock.utils import get_bin @@ -36,7 +37,15 @@ class SubcontractingOrder(SubcontractingController): def validate_purchase_order_for_subcontracting(self): if self.purchase_order: + if is_subcontracting_order_created(self.purchase_order): + frappe.throw( + _( + "Only one Subcontracting Order can be created against a Purchase Order, cancel the existing Subcontracting Order to create a new one." + ) + ) + po = frappe.get_doc("Purchase Order", self.purchase_order) + if not po.is_subcontracted: frappe.throw(_("Please select a valid Purchase Order that is configured for Subcontracting.")) From 687329f5713a31873da575b3ae8b089dd2dead69 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 5 Jul 2022 08:31:31 +0530 Subject: [PATCH 64/98] chore: update fg_item_qty based on qty in PO Item --- .../buying/doctype/purchase_order/purchase_order.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 582f23e311..b5051eb286 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -112,6 +112,16 @@ frappe.ui.form.on("Purchase Order Item", { set_schedule_date(frm); } } + }, + + qty: function(frm, cdt, cdn) { + if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) { + var row = locals[cdt][cdn]; + + if (row.qty) { + row.fg_item_qty = row.qty; + } + } } }); From 6f7e67db9d7811eabbcb8259471a31c65f6b349a Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 5 Jul 2022 08:44:35 +0530 Subject: [PATCH 65/98] chore: hide "Duplicate" button in PO --- .../buying/doctype/purchase_order/purchase_order.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index b5051eb286..fbb42fe2f6 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -45,8 +45,17 @@ frappe.ui.form.on("Purchase Order", { }, refresh: function(frm) { - if(frm.doc.is_old_subcontracting_flow) + if(frm.doc.is_old_subcontracting_flow) { frm.trigger('get_materials_from_supplier'); + + $('a.grey-link').each(function () { + var id = $(this).children(':first-child').attr('data-label'); + if (id == 'Duplicate') { + $(this).remove(); + return false; + } + }); + } }, get_materials_from_supplier: function(frm) { From a7161d387554798185e4e00dfb64700ad94345ad Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 5 Jul 2022 09:15:28 +0530 Subject: [PATCH 66/98] fix: SCO status on SCR cancel --- .../doctype/subcontracting_order/subcontracting_order.py | 3 ++- .../subcontracting_order/test_subcontracting_order.py | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 73ab43401b..71cdc94a3a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -192,10 +192,11 @@ class SubcontractingOrder(SubcontractingController): status = "Partial Material Transferred" if total_supplied_qty >= total_required_qty: status = "Material Transferred" + else: + status = "Open" elif self.docstatus == 2: status = "Cancelled" - if status: frappe.db.set_value("Subcontracting Order", self.name, "status", status, update_modified) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index e579834963..94bb38e980 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -96,6 +96,12 @@ class TestSubcontractingOrder(FrappeTestCase): sco.load_from_db() self.assertEqual(sco.status, "Completed") + # Partially Received (scr cancelled) + scr.load_from_db() + scr.cancel() + sco.load_from_db() + self.assertEqual(sco.status, "Partially Received") + def test_make_rm_stock_entry(self): sco = get_subcontracting_order() rm_items = get_rm_items(sco.supplied_items) From 900c878e03be5d66a6ff5929826f4e44bb471f26 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 28 Apr 2022 14:54:42 +0530 Subject: [PATCH 67/98] update loan interest accrual types --- .../loan_interest_accrual/loan_interest_accrual.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index 30e2328442..c05deeb938 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -159,7 +159,7 @@ "fieldname": "accrual_type", "fieldtype": "Select", "label": "Accrual Type", - "options": "Regular\nRepayment\nDisbursement" + "options": "Regular\nRepayment\nDisbursement\nInterest Credit\nInterest Debit\nRefund" }, { "fieldname": "penalty_amount", @@ -185,10 +185,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-19 18:26:38.871889", + "modified": "2022-04-28 14:46:38.131536", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -225,5 +226,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From a81da2ea85990de18a8d954d76bdb590320c52dd Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 24 Jun 2022 16:07:16 +0530 Subject: [PATCH 68/98] Add more loan interest accrual types --- .../doctype/loan_interest_accrual/loan_interest_accrual.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index c05deeb938..5fbe26c3d3 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -158,8 +158,9 @@ { "fieldname": "accrual_type", "fieldtype": "Select", + "in_filter": 1, "label": "Accrual Type", - "options": "Regular\nRepayment\nDisbursement\nInterest Credit\nInterest Debit\nRefund" + "options": "Regular\nRepayment\nDisbursement\nCredit Adjustment\nDebit Adjustment\nRefund" }, { "fieldname": "penalty_amount", @@ -185,7 +186,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-04-28 14:46:38.131536", + "modified": "2022-06-24 15:45:07.014679", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", From 88cd780ca17ae1312fa3b4d7a183201cb479e467 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 24 Jun 2022 17:47:15 +0530 Subject: [PATCH 69/98] Add refund amount to loan --- erpnext/loan_management/doctype/loan/loan.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index ef78a640aa..0797084380 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -48,6 +48,7 @@ "total_payment", "total_principal_paid", "written_off_amount", + "refund_amount", "column_break_19", "total_interest_payable", "total_amount_paid", @@ -379,12 +380,20 @@ "fieldtype": "Link", "label": "Cost Center", "options": "Cost Center" + }, + { + "fieldname": "refund_amount", + "fieldtype": "Currency", + "label": "Refund amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-03-10 11:50:31.957360", + "modified": "2022-06-24 16:17:57.018272", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", From e1682965c55b638ca1e0ca6182e59b1b607bdf6c Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Mon, 27 Jun 2022 15:21:39 +0530 Subject: [PATCH 70/98] Add Loan Refund doctype --- .../doctype/loan_refund/__init__.py | 0 .../doctype/loan_refund/loan_refund.js | 8 + .../doctype/loan_refund/loan_refund.json | 176 ++++++++++++++++++ .../doctype/loan_refund/loan_refund.py | 95 ++++++++++ .../doctype/loan_refund/test_loan_refund.py | 9 + 5 files changed, 288 insertions(+) create mode 100644 erpnext/loan_management/doctype/loan_refund/__init__.py create mode 100644 erpnext/loan_management/doctype/loan_refund/loan_refund.js create mode 100644 erpnext/loan_management/doctype/loan_refund/loan_refund.json create mode 100644 erpnext/loan_management/doctype/loan_refund/loan_refund.py create mode 100644 erpnext/loan_management/doctype/loan_refund/test_loan_refund.py diff --git a/erpnext/loan_management/doctype/loan_refund/__init__.py b/erpnext/loan_management/doctype/loan_refund/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.js b/erpnext/loan_management/doctype/loan_refund/loan_refund.js new file mode 100644 index 0000000000..f108bf7a28 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.js @@ -0,0 +1,8 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Loan Refund', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.json b/erpnext/loan_management/doctype/loan_refund/loan_refund.json new file mode 100644 index 0000000000..f78e55e9f9 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.json @@ -0,0 +1,176 @@ +{ + "actions": [], + "autoname": "LM-RF-.#####", + "creation": "2022-06-24 15:51:03.165498", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "loan", + "applicant_type", + "applicant", + "column_break_3", + "company", + "posting_date", + "accounting_dimensions_section", + "cost_center", + "section_break_9", + "refund_account", + "column_break_11", + "refund_amount", + "reference_number", + "amended_from" + ], + "fields": [ + { + "fieldname": "loan", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Loan", + "options": "Loan", + "reqd": 1 + }, + { + "fetch_from": "loan.applicant_type", + "fieldname": "applicant_type", + "fieldtype": "Select", + "label": "Applicant Type", + "options": "Employee\nMember\nCustomer", + "read_only": 1 + }, + { + "fetch_from": "loan.applicant", + "fieldname": "applicant", + "fieldtype": "Dynamic Link", + "label": "Applicant ", + "options": "applicant_type", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "loan.company", + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "read_only": 1, + "reqd": 1 + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Refund Details" + }, + { + "fieldname": "refund_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Refund Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "refund_amount", + "fieldtype": "Currency", + "label": "Refund Amount", + "options": "Company:company:default_currency", + "reqd": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Refund", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Refund", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "reference_number", + "fieldtype": "Data", + "label": "Reference Number" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2022-06-24 16:13:48.793486", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Refund", + "naming_rule": "Expression (old style)", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Loan Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py new file mode 100644 index 0000000000..2a7f47871f --- /dev/null +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -0,0 +1,95 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class LoanRefund(Document): + """ + Add refund if total repayment is more than that is owed. + """ + def validate(self): + self.set_missing_values() + self.validate_refund_amount() + + def set_missing_values(self): + if not self.cost_center: + self.cost_center = erpnext.get_default_cost_center(self.company) + + def validate_refund_amount(self): + precision = cint(frappe.db.get_default("currency_precision")) or 2 + total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value( + "Loan", + self.loan, + ["total_payment", "total_principal_paid", "total_interest_payable", "written_off_amount"], + ) + + excess_amount = flt( + flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount), + precision, + ) + + if self.refund_amount > excess_amount: + frappe.throw(_( + "Refund amount cannot be greater than excess amount {}".format( + excess_amount + ))) + + def on_submit(self): + self.update_outstanding_amount() + self.make_gl_entries() + + def on_cancel(self): + self.update_outstanding_amount(cancel=1) + self.ignore_linked_doctypes = ["GL Entry"] + self.make_gl_entries(cancel=1) + + def update_outstanding_amount(self, cancel=0): + refund_amount = frappe.db.get_value("Loan", self.loan, "refund_amount") + + if cancel: + refund_amount -= self.refund_amount + else: + refund_amount += self.refund_amount + + frappe.db.set_value("Loan", self.loan, "refund_amount", refund_amount) + + def make_gl_entries(self, cancel=0): + gl_entries = [] + loan_details = frappe.get_doc("Loan", self.loan) + + gl_entries.append( + self.get_gl_dict( + { + "account": self.refund_account, + "against": loan_details.loan_account, + "credit": self.refund_amount, + "credit_in_account_currency": self.refund_amount, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _("Against Loan:") + self.loan, + "cost_center": self.cost_center, + "posting_date": getdate(self.posting_date), + } + ) + ) + + gl_entries.append( + self.get_gl_dict( + { + "account": loan_details.loan_account, + "party_type": loan_details.applicant_type, + "party": loan_details.applicant, + "against": self.refund_account, + "debit": self.refund_amount, + "debit_in_account_currency": self.refund_amount, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _("Against Loan:") + self.loan, + "cost_center": self.cost_center, + "posting_date": getdate(self.posting_date), + } + ) + ) + + make_gl_entries(gl_entries, cancel=cancel, merge_entries=False) diff --git a/erpnext/loan_management/doctype/loan_refund/test_loan_refund.py b/erpnext/loan_management/doctype/loan_refund/test_loan_refund.py new file mode 100644 index 0000000000..de2f9e1372 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_refund/test_loan_refund.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestLoanRefund(FrappeTestCase): + pass From 2e8f0565147c2e176c8ab43fbc79686b238e4d23 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Tue, 28 Jun 2022 17:51:11 +0530 Subject: [PATCH 71/98] Add Loan Balance Adjustment doctype --- .../loan_balance_adjustment/__init__.py | 0 .../loan_balance_adjustment.js | 8 + .../loan_balance_adjustment.json | 190 ++++++++++++++++++ .../loan_balance_adjustment.py | 132 ++++++++++++ .../test_loan_balance_adjustment.py | 9 + 5 files changed, 339 insertions(+) create mode 100644 erpnext/loan_management/doctype/loan_balance_adjustment/__init__.py create mode 100644 erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.js create mode 100644 erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json create mode 100644 erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py create mode 100644 erpnext/loan_management/doctype/loan_balance_adjustment/test_loan_balance_adjustment.py diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/__init__.py b/erpnext/loan_management/doctype/loan_balance_adjustment/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.js b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.js new file mode 100644 index 0000000000..8aec63ad75 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.js @@ -0,0 +1,8 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Loan Balance Adjustment', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json new file mode 100644 index 0000000000..5fb25defce --- /dev/null +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json @@ -0,0 +1,190 @@ +{ + "actions": [], + "autoname": "LM-ADJ-.#####", + "creation": "2022-06-28 14:48:47.736269", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "loan", + "applicant_type", + "applicant", + "column_break_3", + "company", + "posting_date", + "accounting_dimensions_section", + "cost_center", + "section_break_9", + "adjustment_account", + "column_break_11", + "adjustment_type", + "amount", + "reference_number", + "remarks", + "amended_from" + ], + "fields": [ + { + "fieldname": "loan", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Loan", + "options": "Loan", + "reqd": 1 + }, + { + "fetch_from": "loan.applicant_type", + "fieldname": "applicant_type", + "fieldtype": "Select", + "label": "Applicant Type", + "options": "Employee\nMember\nCustomer", + "read_only": 1 + }, + { + "fetch_from": "loan.applicant", + "fieldname": "applicant", + "fieldtype": "Dynamic Link", + "label": "Applicant ", + "options": "applicant_type", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "loan.company", + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "read_only": 1, + "reqd": 1 + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Adjustment Details" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "reference_number", + "fieldtype": "Data", + "label": "Reference Number" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Balance Adjustment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Balance Adjustment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "adjustment_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Adjustment Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "options": "Company:company:default_currency", + "reqd": 1 + }, + { + "fieldname": "adjustment_type", + "fieldtype": "Select", + "label": "Adjustment Type", + "options": "Credit Adjustment\nDebit Adjustment", + "reqd": 1 + }, + { + "fieldname": "remarks", + "fieldtype": "Data", + "label": "Remarks" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2022-06-28 14:54:52.792592", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Balance Adjustment", + "naming_rule": "Expression (old style)", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Loan Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py new file mode 100644 index 0000000000..8f98ce64d8 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -0,0 +1,132 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class LoanBalanceAdjustment(Document): + """ + Add credit/debit adjustments to loan ledger. + """ + def validate(self): + self.set_missing_values() + + def on_submit(self): + self.set_status_and_amounts() + self.make_gl_entries() + + def on_cancel(self): + self.set_status_and_amounts(cancel=1) + self.make_gl_entries(cancel=1) + self.ignore_linked_doctypes = ["GL Entry"] + + def set_missing_values(self): + if not self.posting_date: + self.posting_date = nowdate() + + if not self.cost_center: + self.cost_center = erpnext.get_default_cost_center(self.company) + + if not self.posting_date: + self.posting_date = self.posting_date or nowdate() + + def set_status_and_amounts(self, cancel=0): + loan_details = frappe.get_all( + "Loan", + fields=[ + "loan_amount", + "disbursed_amount", + "total_payment", + "total_principal_paid", + "total_interest_payable", + "status", + "is_term_loan", + "is_secured_loan", + ], + filters={"name": self.against_loan}, + )[0] + + if cancel: + disbursed_amount = self.get_values_on_cancel(loan_details) + else: + disbursed_amount = self.get_values_on_submit(loan_details) + + frappe.db.set_value( + "Loan", + self.against_loan, + { + "disbursed_amount": disbursed_amount, + }, + ) + + def get_values_on_cancel(self, loan_details): + if self.adjustment_type == "Credit Adjustment": + disbursed_amount = loan_details.disbursed_amount - self.amount + elif self.adjustment_type == "Debit Adjustment": + disbursed_amount = loan_details.disbursed_amount + self.amount + + return disbursed_amount + + def get_values_on_submit(self, loan_details): + if self.adjustment_type == "Credit": + disbursed_amount = loan_details.disbursed_amount + self.amount + elif self.adjustment_type == "Debit": + disbursed_amount = loan_details.disbursed_amount - self.amount + + total_payment = loan_details.total_payment + + if loan_details.status in ("Disbursed", "Partially Disbursed") and not loan_details.is_term_loan: + process_loan_interest_accrual_for_demand_loans( + posting_date=add_days(self.posting_date, -1), + loan=self.against_loan, + accrual_type=self.adjustment_type, + ) + + return disbursed_amount + + def make_gl_entries(self, cancel=0, adv_adj=0): + gle_map = [] + + loan_entry = { + "account": self.loan_account, + "against": self.adjustment_account, + "against_voucher_type": "Loan", + "against_voucher": self.against_loan, + "remarks": _("{} against loan:".format(self.adjustment_type)) \ + + self.against_loan, + "cost_center": self.cost_center, + "party_type": self.applicant_type, + "party": self.applicant, + "posting_date": self.posting_date, + } + company_entry = { + "account": self.adjustment_account, + "against": self.loan_account, + "against_voucher_type": "Loan", + "against_voucher": self.against_loan, + "remarks": _("{} against loan:".format(self.adjustment_type)) \ + + self.against_loan, + "cost_center": self.cost_center, + "posting_date": self.posting_date, + } + if self.adjustment_type == "Credit Adjustment": + loan_entry["credit"] = self.amount + loan_entry["credit_in_account_currency"] = self.amount + + company_entry["debit"] = self.amount + company_entry["debit_in_account_currency"] = self.amount + + elif self.adjustment_type == "Debit Adjustment": + loan_entry["debit"] = self.amount + loan_entry["debit_in_account_currency"] = self.amount + + company_entry["credit"] = self.amount + company_entry["credit_in_account_currency"] = self.amount + + + gle_map.append(self.get_gl_dict(loan_entry)) + + gle_map.append(self.get_gl_dict(company_entry)) + + if gle_map: + make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/test_loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/test_loan_balance_adjustment.py new file mode 100644 index 0000000000..7658d7b215 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/test_loan_balance_adjustment.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestLoanBalanceAdjustment(FrappeTestCase): + pass From 5c0a25012c602ed0d47136468e3b0bee11ddf5dd Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Wed, 29 Jun 2022 14:16:23 +0530 Subject: [PATCH 72/98] feat: add adjustment amount to loan - fix: bugs in loan balance adjustment --- .../loan_management/doctype/loan/loan.json | 13 +- .../loan_balance_adjustment.json | 11 +- .../loan_balance_adjustment.py | 258 +++++++++--------- .../doctype/loan_repayment/loan_repayment.py | 2 + .../process_loan_interest_accrual.json | 6 +- 5 files changed, 160 insertions(+), 130 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index 0797084380..f4ab7ed835 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -49,6 +49,7 @@ "total_principal_paid", "written_off_amount", "refund_amount", + "adjustment_amount", "column_break_19", "total_interest_payable", "total_amount_paid", @@ -388,12 +389,20 @@ "no_copy": 1, "options": "Company:company:default_currency", "read_only": 1 - } + }, + { + "fieldname": "adjustment_amount", + "fieldtype": "Currency", + "label": "Adjustment amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-06-24 16:17:57.018272", + "modified": "2022-06-29 13:17:57.018272", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json index 5fb25defce..35be555cb7 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json @@ -115,12 +115,13 @@ "read_only": 1 }, { + "fetch_from": "loan.payment_account", "fieldname": "adjustment_account", "fieldtype": "Link", "in_list_view": 1, "label": "Adjustment Account", "options": "Account", - "reqd": 1 + "read_only": 1 }, { "fieldname": "amount", @@ -129,6 +130,14 @@ "options": "Company:company:default_currency", "reqd": 1 }, + { + "fetch_from": "loan.loan_account", + "fieldname": "loan_account", + "fieldtype": "Link", + "label": "Loan Account", + "options": "Account", + "read_only": 1 + }, { "fieldname": "adjustment_type", "fieldtype": "Select", diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index 8f98ce64d8..4b57060ce8 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -1,132 +1,140 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe -from frappe.model.document import Document - -class LoanBalanceAdjustment(Document): - """ - Add credit/debit adjustments to loan ledger. - """ - def validate(self): - self.set_missing_values() - - def on_submit(self): - self.set_status_and_amounts() - self.make_gl_entries() - - def on_cancel(self): - self.set_status_and_amounts(cancel=1) - self.make_gl_entries(cancel=1) - self.ignore_linked_doctypes = ["GL Entry"] - - def set_missing_values(self): - if not self.posting_date: - self.posting_date = nowdate() - - if not self.cost_center: - self.cost_center = erpnext.get_default_cost_center(self.company) - - if not self.posting_date: - self.posting_date = self.posting_date or nowdate() - - def set_status_and_amounts(self, cancel=0): - loan_details = frappe.get_all( - "Loan", - fields=[ - "loan_amount", - "disbursed_amount", - "total_payment", - "total_principal_paid", - "total_interest_payable", - "status", - "is_term_loan", - "is_secured_loan", - ], - filters={"name": self.against_loan}, - )[0] - - if cancel: - disbursed_amount = self.get_values_on_cancel(loan_details) - else: - disbursed_amount = self.get_values_on_submit(loan_details) - - frappe.db.set_value( - "Loan", - self.against_loan, - { - "disbursed_amount": disbursed_amount, - }, - ) - - def get_values_on_cancel(self, loan_details): - if self.adjustment_type == "Credit Adjustment": - disbursed_amount = loan_details.disbursed_amount - self.amount - elif self.adjustment_type == "Debit Adjustment": - disbursed_amount = loan_details.disbursed_amount + self.amount - - return disbursed_amount - - def get_values_on_submit(self, loan_details): - if self.adjustment_type == "Credit": - disbursed_amount = loan_details.disbursed_amount + self.amount - elif self.adjustment_type == "Debit": - disbursed_amount = loan_details.disbursed_amount - self.amount - - total_payment = loan_details.total_payment - - if loan_details.status in ("Disbursed", "Partially Disbursed") and not loan_details.is_term_loan: - process_loan_interest_accrual_for_demand_loans( - posting_date=add_days(self.posting_date, -1), - loan=self.against_loan, - accrual_type=self.adjustment_type, - ) - - return disbursed_amount - - def make_gl_entries(self, cancel=0, adv_adj=0): - gle_map = [] - - loan_entry = { - "account": self.loan_account, - "against": self.adjustment_account, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": _("{} against loan:".format(self.adjustment_type)) \ - + self.against_loan, - "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, - "posting_date": self.posting_date, - } - company_entry = { - "account": self.adjustment_account, - "against": self.loan_account, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": _("{} against loan:".format(self.adjustment_type)) \ - + self.against_loan, - "cost_center": self.cost_center, - "posting_date": self.posting_date, - } - if self.adjustment_type == "Credit Adjustment": - loan_entry["credit"] = self.amount - loan_entry["credit_in_account_currency"] = self.amount - - company_entry["debit"] = self.amount - company_entry["debit_in_account_currency"] = self.amount - - elif self.adjustment_type == "Debit Adjustment": - loan_entry["debit"] = self.amount - loan_entry["debit_in_account_currency"] = self.amount - - company_entry["credit"] = self.amount - company_entry["credit_in_account_currency"] = self.amount +import frappe +import erpnext +from frappe import _ +from frappe.utils import nowdate, add_days +from erpnext.controllers.accounts_controller import AccountsController +from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( + process_loan_interest_accrual_for_demand_loans, +) +from erpnext.accounts.general_ledger import make_gl_entries - gle_map.append(self.get_gl_dict(loan_entry)) +class LoanBalanceAdjustment(AccountsController): + """ + Add credit/debit adjustments to loan ledger. + """ - gle_map.append(self.get_gl_dict(company_entry)) + def validate(self): + if self.amount == 0: + frappe.throw(_("Amount cannot be zero")) + if self.amount < 0: + frappe.throw(_("Amount cannot be negative")) + self.set_missing_values() - if gle_map: - make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj) + def on_submit(self): + self.set_status_and_amounts() + self.make_gl_entries() + + def on_cancel(self): + self.set_status_and_amounts(cancel=1) + self.make_gl_entries(cancel=1) + self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] + + def set_missing_values(self): + if not self.posting_date: + self.posting_date = nowdate() + + if not self.cost_center: + self.cost_center = erpnext.get_default_cost_center(self.company) + + def set_status_and_amounts(self, cancel=0): + loan_details = frappe.get_all( + "Loan", + fields=[ + "loan_amount", + "adjustment_amount", + "total_payment", + "total_principal_paid", + "total_interest_payable", + "status", + "is_term_loan", + "is_secured_loan", + ], + filters={"name": self.loan}, + )[0] + + if cancel: + adjustment_amount = self.get_values_on_cancel(loan_details) + else: + adjustment_amount = self.get_values_on_submit(loan_details) + + frappe.db.set_value( + "Loan", + self.loan, + { + "adjustment_amount": adjustment_amount, + }, + ) + + def get_values_on_cancel(self, loan_details): + if self.adjustment_type == "Credit Adjustment": + adjustment_amount = loan_details.adjustment_amount - self.amount + elif self.adjustment_type == "Debit Adjustment": + adjustment_amount = loan_details.adjustment_amount + self.amount + + return adjustment_amount + + def get_values_on_submit(self, loan_details): + if self.adjustment_type == "Credit Adjustment": + adjustment_amount = loan_details.adjustment_amount + self.amount + elif self.adjustment_type == "Debit Adjustment": + adjustment_amount = loan_details.adjustment_amount - self.amount + + if ( + loan_details.status in ("Disbursed", "Partially Disbursed") + and not loan_details.is_term_loan + ): + process_loan_interest_accrual_for_demand_loans( + posting_date=add_days(self.posting_date, -1), + loan=self.loan, + accrual_type=self.adjustment_type, + ) + + return adjustment_amount + + def make_gl_entries(self, cancel=0, adv_adj=0): + gle_map = [] + + loan_entry = { + "account": self.loan_account, + "against": self.adjustment_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _("{} against loan:".format(self.adjustment_type)) + self.loan, + "cost_center": self.cost_center, + "party_type": self.applicant_type, + "party": self.applicant, + "posting_date": self.posting_date, + } + company_entry = { + "account": self.adjustment_account, + "against": self.loan_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _("{} against loan:".format(self.adjustment_type)) + self.loan, + "cost_center": self.cost_center, + "posting_date": self.posting_date, + } + if self.adjustment_type == "Credit Adjustment": + loan_entry["credit"] = self.amount + loan_entry["credit_in_account_currency"] = self.amount + + company_entry["debit"] = self.amount + company_entry["debit_in_account_currency"] = self.amount + + elif self.adjustment_type == "Debit Adjustment": + loan_entry["debit"] = self.amount + loan_entry["debit_in_account_currency"] = self.amount + + company_entry["credit"] = self.amount + company_entry["credit_in_account_currency"] = self.amount + + gle_map.append(self.get_gl_dict(loan_entry)) + + gle_map.append(self.get_gl_dict(company_entry)) + + if gle_map: + make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 51f40d9a20..32b257b7d0 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -623,6 +623,7 @@ def get_pending_principal_amount(loan): if loan.status in ("Disbursed", "Closed") or loan.disbursed_amount >= loan.loan_amount: pending_principal_amount = ( flt(loan.total_payment) + + flt(loan.adjustment_amount) - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) @@ -630,6 +631,7 @@ def get_pending_principal_amount(loan): else: pending_principal_amount = ( flt(loan.disbursed_amount) + + flt(loan.adjustment_amount) - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json index 828df2e35f..7fc4736216 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json @@ -54,17 +54,18 @@ "fieldtype": "Select", "hidden": 1, "label": "Accrual Type", - "options": "Regular\nRepayment\nDisbursement", + "options": "Regular\nRepayment\nDisbursement\nCredit Adjustment\nDebit Adjustment\nRefund", "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-06 13:28:51.478909", + "modified": "2022-06-29 11:19:33.203088", "modified_by": "Administrator", "module": "Loan Management", "name": "Process Loan Interest Accrual", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -98,5 +99,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From d6f632a7701894e6e09f3ee10ce8dccb22e848c5 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 11:29:17 +0530 Subject: [PATCH 73/98] Seperate credit and debit adjust amount fields in Loan --- .../loan_management/doctype/loan/loan.json | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index f4ab7ed835..68d511ef40 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -49,11 +49,12 @@ "total_principal_paid", "written_off_amount", "refund_amount", - "adjustment_amount", "column_break_19", "total_interest_payable", "total_amount_paid", - "amended_from" + "amended_from", + "credit_adjustment_amount", + "debit_adjustment_amount" ], "fields": [ { @@ -391,18 +392,22 @@ "read_only": 1 }, { - "fieldname": "adjustment_amount", - "fieldtype": "Currency", - "label": "Adjustment amount", - "no_copy": 1, - "options": "Company:company:default_currency", - "read_only": 1 - } + "fieldname": "credit_adjustment_amount", + "fieldtype": "Currency", + "label": "Credit Adjustment Amount", + "options": "Company:company:default_currency" + }, + { + "fieldname": "debit_adjustment_amount", + "fieldtype": "Currency", + "label": "Debit Adjustment Amount", + "options": "Company:company:default_currency" + } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-06-29 13:17:57.018272", + "modified": "2022-06-30 11:27:54.281113", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", From 7d468a8778a32abf14f5373f51380f96cf8ce24e Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 11:42:01 +0530 Subject: [PATCH 74/98] Use new adjustment amount fields --- .../loan_balance_adjustment.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index 4b57060ce8..69ab6468be 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -61,27 +61,28 @@ class LoanBalanceAdjustment(AccountsController): else: adjustment_amount = self.get_values_on_submit(loan_details) + if self.adjustment_type == "Credit Adjustment": + adj_field = "credit_adjustment_amount" + elif self.adjustment_type == "Debit Adjustment": + adj_field = "debit_adjustment_amount" + frappe.db.set_value( - "Loan", - self.loan, - { - "adjustment_amount": adjustment_amount, - }, + "Loan", self.loan, {adj_field: adjustment_amount} ) def get_values_on_cancel(self, loan_details): if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.adjustment_amount - self.amount + adjustment_amount = loan_details.credit_adjustment_amount - self.amount elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.adjustment_amount + self.amount + adjustment_amount = loan_details.debit_adjustment_amount - self.amount return adjustment_amount def get_values_on_submit(self, loan_details): if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.adjustment_amount + self.amount + adjustment_amount = loan_details.credit_adjustment_amount + self.amount elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.adjustment_amount - self.amount + adjustment_amount = loan_details.debit_adjustment_amount + self.amount if ( loan_details.status in ("Disbursed", "Partially Disbursed") @@ -98,8 +99,10 @@ class LoanBalanceAdjustment(AccountsController): def make_gl_entries(self, cancel=0, adv_adj=0): gle_map = [] + loan_account = frappe.db.get_value("Loan", self.loan, "loan_account") + loan_entry = { - "account": self.loan_account, + "account": loan_account, "against": self.adjustment_account, "against_voucher_type": "Loan", "against_voucher": self.loan, @@ -111,7 +114,7 @@ class LoanBalanceAdjustment(AccountsController): } company_entry = { "account": self.adjustment_account, - "against": self.loan_account, + "against": loan_account, "against_voucher_type": "Loan", "against_voucher": self.loan, "remarks": _("{} against loan:".format(self.adjustment_type)) + self.loan, From 6febcd529b088aaa86991b11b9941b537ac67e9d Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 11:42:33 +0530 Subject: [PATCH 75/98] Remove loan account field from doctype --- .../loan_balance_adjustment.json | 384 +++++++++--------- 1 file changed, 187 insertions(+), 197 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json index 35be555cb7..cfbde0f6ce 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json @@ -1,199 +1,189 @@ { - "actions": [], - "autoname": "LM-ADJ-.#####", - "creation": "2022-06-28 14:48:47.736269", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "loan", - "applicant_type", - "applicant", - "column_break_3", - "company", - "posting_date", - "accounting_dimensions_section", - "cost_center", - "section_break_9", - "adjustment_account", - "column_break_11", - "adjustment_type", - "amount", - "reference_number", - "remarks", - "amended_from" - ], - "fields": [ - { - "fieldname": "loan", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Loan", - "options": "Loan", - "reqd": 1 - }, - { - "fetch_from": "loan.applicant_type", - "fieldname": "applicant_type", - "fieldtype": "Select", - "label": "Applicant Type", - "options": "Employee\nMember\nCustomer", - "read_only": 1 - }, - { - "fetch_from": "loan.applicant", - "fieldname": "applicant", - "fieldtype": "Dynamic Link", - "label": "Applicant ", - "options": "applicant_type", - "read_only": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fetch_from": "loan.company", - "fieldname": "company", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Company", - "options": "Company", - "read_only": 1, - "reqd": 1 - }, - { - "default": "Today", - "fieldname": "posting_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Posting Date", - "reqd": 1 - }, - { - "collapsible": 1, - "fieldname": "accounting_dimensions_section", - "fieldtype": "Section Break", - "label": "Accounting Dimensions" - }, - { - "fieldname": "cost_center", - "fieldtype": "Link", - "label": "Cost Center", - "options": "Cost Center" - }, - { - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "label": "Adjustment Details" - }, - { - "fieldname": "column_break_11", - "fieldtype": "Column Break" - }, - { - "fieldname": "reference_number", - "fieldtype": "Data", - "label": "Reference Number" - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Loan Balance Adjustment", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Loan Balance Adjustment", - "print_hide": 1, - "read_only": 1 - }, - { - "fetch_from": "loan.payment_account", - "fieldname": "adjustment_account", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Adjustment Account", - "options": "Account", - "read_only": 1 - }, - { - "fieldname": "amount", - "fieldtype": "Currency", - "label": "Amount", - "options": "Company:company:default_currency", - "reqd": 1 - }, - { - "fetch_from": "loan.loan_account", - "fieldname": "loan_account", - "fieldtype": "Link", - "label": "Loan Account", - "options": "Account", - "read_only": 1 - }, - { - "fieldname": "adjustment_type", - "fieldtype": "Select", - "label": "Adjustment Type", - "options": "Credit Adjustment\nDebit Adjustment", - "reqd": 1 - }, - { - "fieldname": "remarks", - "fieldtype": "Data", - "label": "Remarks" - } - ], - "index_web_pages_for_search": 1, - "is_submittable": 1, - "links": [], - "modified": "2022-06-28 14:54:52.792592", - "modified_by": "Administrator", - "module": "Loan Management", - "name": "Loan Balance Adjustment", - "naming_rule": "Expression (old style)", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Loan Manager", - "share": 1, - "submit": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 + "actions": [], + "autoname": "LM-ADJ-.#####", + "creation": "2022-06-28 14:48:47.736269", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "loan", + "applicant_type", + "applicant", + "column_break_3", + "company", + "posting_date", + "accounting_dimensions_section", + "cost_center", + "section_break_9", + "adjustment_account", + "column_break_11", + "adjustment_type", + "amount", + "reference_number", + "remarks", + "amended_from" + ], + "fields": [ + { + "fieldname": "loan", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Loan", + "options": "Loan", + "reqd": 1 + }, + { + "fetch_from": "loan.applicant_type", + "fieldname": "applicant_type", + "fieldtype": "Select", + "label": "Applicant Type", + "options": "Employee\nMember\nCustomer", + "read_only": 1 + }, + { + "fetch_from": "loan.applicant", + "fieldname": "applicant", + "fieldtype": "Dynamic Link", + "label": "Applicant ", + "options": "applicant_type", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "loan.company", + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "read_only": 1, + "reqd": 1 + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Adjustment Details" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "reference_number", + "fieldtype": "Data", + "label": "Reference Number" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Balance Adjustment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Balance Adjustment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "adjustment_account", + "fieldtype": "Link", + "label": "Adjustment Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "options": "Company:company:default_currency", + "reqd": 1 + }, + { + "fieldname": "adjustment_type", + "fieldtype": "Select", + "label": "Adjustment Type", + "options": "Credit\nDebit", + "reqd": 1 + }, + { + "fieldname": "remarks", + "fieldtype": "Data", + "label": "Remarks" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2022-06-30 11:38:16.631994", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Balance Adjustment", + "naming_rule": "Expression (old style)", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Loan Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 } \ No newline at end of file From 7d6e4898c48a28405d78c58f41f7f99b316989be Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 11:59:38 +0530 Subject: [PATCH 76/98] Update list view for Accrual and Shortfall --- .../loan_interest_accrual.json | 9 ++++++- .../loan_security_shortfall.json | 25 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index 5fbe26c3d3..08dc98c830 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -35,12 +35,15 @@ { "fieldname": "loan", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Loan", "options": "Loan" }, { "fieldname": "posting_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Posting Date" }, { @@ -75,6 +78,8 @@ "fetch_from": "loan.applicant", "fieldname": "applicant", "fieldtype": "Dynamic Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Applicant", "options": "applicant_type" }, @@ -159,6 +164,8 @@ "fieldname": "accrual_type", "fieldtype": "Select", "in_filter": 1, + "in_list_view": 1, + "in_standard_filter": 1, "label": "Accrual Type", "options": "Regular\nRepayment\nDisbursement\nCredit Adjustment\nDebit Adjustment\nRefund" }, @@ -186,7 +193,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-06-24 15:45:07.014679", + "modified": "2022-06-30 11:51:31.911794", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json index 99b5c72b2d..d4007cb62d 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json @@ -7,6 +7,8 @@ "engine": "InnoDB", "field_order": [ "loan", + "applicant_type", + "applicant", "status", "column_break_3", "shortfall_time", @@ -23,6 +25,8 @@ { "fieldname": "loan", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Loan ", "options": "Loan", "read_only": 1 @@ -91,17 +95,35 @@ { "fieldname": "shortfall_percentage", "fieldtype": "Percent", + "in_list_view": 1, "label": "Shortfall Percentage", "read_only": 1 + }, + { + "fetch_from": "loan.applicant_type", + "fieldname": "applicant_type", + "fieldtype": "Select", + "label": "Applicant Type", + "options": "Employee\nMember\nCustomer" + }, + { + "fetch_from": "loan.applicant", + "fieldname": "applicant", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Applicant", + "options": "applicant_type" } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-04-01 08:13:43.263772", + "modified": "2022-06-30 11:57:09.378089", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Shortfall", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -132,5 +154,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From 27a8e16b28e5876b4dc965039addf626956f5bbe Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 12:04:45 +0530 Subject: [PATCH 77/98] Add NPA checkbox in Loan --- erpnext/loan_management/doctype/loan/loan.json | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index 68d511ef40..fb5f1339ba 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -49,12 +49,13 @@ "total_principal_paid", "written_off_amount", "refund_amount", + "debit_adjustment_amount", + "credit_adjustment_amount", + "is_npa", "column_break_19", "total_interest_payable", "total_amount_paid", - "amended_from", - "credit_adjustment_amount", - "debit_adjustment_amount" + "amended_from" ], "fields": [ { @@ -402,12 +403,19 @@ "fieldtype": "Currency", "label": "Debit Adjustment Amount", "options": "Company:company:default_currency" + }, + { + "default": "0", + "description": "Mark Loan as a Nonperforming asset", + "fieldname": "is_npa", + "fieldtype": "Check", + "label": "Is NPA" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-06-30 11:27:54.281113", + "modified": "2022-06-30 12:04:13.728880", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", From 74dbf8c5d93ce389813f88e43d01b5d70b899148 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 13:50:40 +0530 Subject: [PATCH 78/98] Add reference number to repayment remarks --- .../doctype/loan_repayment/loan_repayment.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 32b257b7d0..3c2e5003fe 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -386,15 +386,19 @@ class LoanRepayment(AccountsController): def make_gl_entries(self, cancel=0, adv_adj=0): gle_map = [] - if self.shortfall_amount and self.amount_paid > self.shortfall_amount: - remarks = _("Shortfall Repayment of {0}.
Repayment against Loan: {1}").format( + remarks = "Shortfall repayment of {0}.
Repayment against loan {1}".format( self.shortfall_amount, self.against_loan ) elif self.shortfall_amount: - remarks = _("Shortfall Repayment of {0}").format(self.shortfall_amount) + remarks = "Shortfall repayment of {0} against loan {1}".format( + self.shortfall_amount, self.against_loan + ) else: - remarks = _("Repayment against Loan:") + " " + self.against_loan + remarks = "Repayment against loan " + self.against_loan + + if self.reference_number: + remarks += "with reference no. {}".format(self.reference_number) if self.repay_from_salary: payment_account = self.payroll_payable_account @@ -445,7 +449,7 @@ class LoanRepayment(AccountsController): "debit_in_account_currency": self.amount_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": remarks, + "remarks": _(remarks), "cost_center": self.cost_center, "posting_date": getdate(self.posting_date), } @@ -463,7 +467,7 @@ class LoanRepayment(AccountsController): "credit_in_account_currency": self.amount_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": remarks, + "remarks": _(remarks), "cost_center": self.cost_center, "posting_date": getdate(self.posting_date), } From a1a51ce1a6b6ab2415078eb94d5b19b66b8e16ed Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 13:58:13 +0530 Subject: [PATCH 79/98] Add ref no to balance adjustment remarks --- .../loan_balance_adjustment/loan_balance_adjustment.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index 69ab6468be..a582cfecc4 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -100,13 +100,18 @@ class LoanBalanceAdjustment(AccountsController): gle_map = [] loan_account = frappe.db.get_value("Loan", self.loan, "loan_account") + remarks = "{} against loan {}".format( + self.adjustment_type.capitalize(), self.loan + ) + if self.reference_number: + remarks += "with reference no. {}".format(self.reference_number) loan_entry = { "account": loan_account, "against": self.adjustment_account, "against_voucher_type": "Loan", "against_voucher": self.loan, - "remarks": _("{} against loan:".format(self.adjustment_type)) + self.loan, + "remarks": _(remarks), "cost_center": self.cost_center, "party_type": self.applicant_type, "party": self.applicant, @@ -117,7 +122,7 @@ class LoanBalanceAdjustment(AccountsController): "against": loan_account, "against_voucher_type": "Loan", "against_voucher": self.loan, - "remarks": _("{} against loan:".format(self.adjustment_type)) + self.loan, + "remarks": _(remarks), "cost_center": self.cost_center, "posting_date": self.posting_date, } From 1b5b2138ee95d376d77b07ea2f4c06946971a13a Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 14:04:19 +0530 Subject: [PATCH 80/98] Use adjustment amounts in pending principal amnt --- .../doctype/loan_repayment/loan_repayment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 3c2e5003fe..7b0028deb4 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -627,7 +627,8 @@ def get_pending_principal_amount(loan): if loan.status in ("Disbursed", "Closed") or loan.disbursed_amount >= loan.loan_amount: pending_principal_amount = ( flt(loan.total_payment) - + flt(loan.adjustment_amount) + + flt(loan.debit_adjustment_amount) + - flt(loan.credit_adjustment_amount) - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) @@ -635,7 +636,8 @@ def get_pending_principal_amount(loan): else: pending_principal_amount = ( flt(loan.disbursed_amount) - + flt(loan.adjustment_amount) + + flt(loan.debit_adjustment_amount) + - flt(loan.credit_adjustment_amount) - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) From 88ac519b2439603a65b720f935887fd7287005be Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 7 Jul 2022 16:59:23 +0530 Subject: [PATCH 81/98] fix: Use Contact Name instead of Supplier in RFQ Email --- .../request_for_quotation/request_for_quotation.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 67affe770f..2fda831d75 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -180,12 +180,19 @@ class RequestforQuotation(BuyingController): doc_args = self.as_dict() doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")}) + # Get Contact Full Name + if data.get("contact"): + contact_name = frappe.db.get_value( + "Contact", data.get("contact"), ["first_name", "middle_name", "last_name"] + ) + supplier_name = (" ").join(x for x in contact_name if x) # remove any blank values + args = { "update_password_link": update_password_link, "message": frappe.render_template(self.message_for_supplier, doc_args), "rfq_link": rfq_link, "user_fullname": full_name, - "supplier_name": data.get("supplier_name"), + "supplier_name": supplier_name or data.get("supplier_name"), "supplier_salutation": self.salutation or "Dear Mx.", } From 8434ec09c39b08652d7f6463bd77111e9b3a974a Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 7 Jul 2022 17:20:21 +0530 Subject: [PATCH 82/98] fix lint --- .../loan_balance_adjustment.py | 219 +++++++++--------- 1 file changed, 109 insertions(+), 110 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index a582cfecc4..92e9b6acde 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -7,142 +7,141 @@ from frappe import _ from frappe.utils import nowdate, add_days from erpnext.controllers.accounts_controller import AccountsController from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( - process_loan_interest_accrual_for_demand_loans, + process_loan_interest_accrual_for_demand_loans, ) from erpnext.accounts.general_ledger import make_gl_entries class LoanBalanceAdjustment(AccountsController): - """ - Add credit/debit adjustments to loan ledger. - """ + """ + Add credit/debit adjustments to loan ledger. + """ - def validate(self): - if self.amount == 0: - frappe.throw(_("Amount cannot be zero")) - if self.amount < 0: - frappe.throw(_("Amount cannot be negative")) - self.set_missing_values() + def validate(self): + if self.amount == 0: + frappe.throw(_("Amount cannot be zero")) + if self.amount < 0: + frappe.throw(_("Amount cannot be negative")) + self.set_missing_values() - def on_submit(self): - self.set_status_and_amounts() - self.make_gl_entries() + def on_submit(self): + self.set_status_and_amounts() + self.make_gl_entries() - def on_cancel(self): - self.set_status_and_amounts(cancel=1) - self.make_gl_entries(cancel=1) - self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] + def on_cancel(self): + self.set_status_and_amounts(cancel=1) + self.make_gl_entries(cancel=1) + self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] - def set_missing_values(self): - if not self.posting_date: - self.posting_date = nowdate() + def set_missing_values(self): + if not self.posting_date: + self.posting_date = nowdate() - if not self.cost_center: - self.cost_center = erpnext.get_default_cost_center(self.company) + if not self.cost_center: + self.cost_center = erpnext.get_default_cost_center(self.company) - def set_status_and_amounts(self, cancel=0): - loan_details = frappe.get_all( - "Loan", - fields=[ - "loan_amount", - "adjustment_amount", - "total_payment", - "total_principal_paid", - "total_interest_payable", - "status", - "is_term_loan", - "is_secured_loan", - ], - filters={"name": self.loan}, - )[0] + def set_status_and_amounts(self, cancel=0): + loan_details = frappe.get_all( + "Loan", + fields=[ + "loan_amount", + "adjustment_amount", + "total_payment", + "total_principal_paid", + "total_interest_payable", + "status", + "is_term_loan", + "is_secured_loan", + ], + filters={"name": self.loan}, + )[0] - if cancel: - adjustment_amount = self.get_values_on_cancel(loan_details) - else: - adjustment_amount = self.get_values_on_submit(loan_details) + if cancel: + adjustment_amount = self.get_values_on_cancel(loan_details) + else: + adjustment_amount = self.get_values_on_submit(loan_details) - if self.adjustment_type == "Credit Adjustment": - adj_field = "credit_adjustment_amount" - elif self.adjustment_type == "Debit Adjustment": - adj_field = "debit_adjustment_amount" + if self.adjustment_type == "Credit Adjustment": + adj_field = "credit_adjustment_amount" + elif self.adjustment_type == "Debit Adjustment": + adj_field = "debit_adjustment_amount" - frappe.db.set_value( - "Loan", self.loan, {adj_field: adjustment_amount} - ) + frappe.db.set_value( + "Loan", self.loan, {adj_field: adjustment_amount} + ) - def get_values_on_cancel(self, loan_details): - if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.credit_adjustment_amount - self.amount - elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.debit_adjustment_amount - self.amount + def get_values_on_cancel(self, loan_details): + if self.adjustment_type == "Credit Adjustment": + adjustment_amount = loan_details.credit_adjustment_amount - self.amount + elif self.adjustment_type == "Debit Adjustment": + adjustment_amount = loan_details.debit_adjustment_amount - self.amount - return adjustment_amount + return adjustment_amount - def get_values_on_submit(self, loan_details): - if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.credit_adjustment_amount + self.amount - elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.debit_adjustment_amount + self.amount + def get_values_on_submit(self, loan_details): + if self.adjustment_type == "Credit Adjustment": + adjustment_amount = loan_details.credit_adjustment_amount + self.amount + elif self.adjustment_type == "Debit Adjustment": + adjustment_amount = loan_details.debit_adjustment_amount + self.amount - if ( - loan_details.status in ("Disbursed", "Partially Disbursed") - and not loan_details.is_term_loan - ): - process_loan_interest_accrual_for_demand_loans( - posting_date=add_days(self.posting_date, -1), - loan=self.loan, - accrual_type=self.adjustment_type, - ) + if ( + loan_details.status in ("Disbursed", "Partially Disbursed") + and not loan_details.is_term_loan + ): + process_loan_interest_accrual_for_demand_loans( + posting_date=add_days(self.posting_date, -1), + loan=self.loan, + accrual_type=self.adjustment_type, + ) - return adjustment_amount + return adjustment_amount - def make_gl_entries(self, cancel=0, adv_adj=0): - gle_map = [] - - loan_account = frappe.db.get_value("Loan", self.loan, "loan_account") - remarks = "{} against loan {}".format( - self.adjustment_type.capitalize(), self.loan - ) + def make_gl_entries(self, cancel=0, adv_adj=0): + gle_map = [] + loan_account = frappe.db.get_value("Loan", self.loan, "loan_account") + remarks = "{} against loan {}".format( + self.adjustment_type.capitalize(), self.loan + ) if self.reference_number: remarks += "with reference no. {}".format(self.reference_number) - loan_entry = { - "account": loan_account, - "against": self.adjustment_account, - "against_voucher_type": "Loan", - "against_voucher": self.loan, - "remarks": _(remarks), - "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, - "posting_date": self.posting_date, - } - company_entry = { - "account": self.adjustment_account, - "against": loan_account, - "against_voucher_type": "Loan", - "against_voucher": self.loan, - "remarks": _(remarks), - "cost_center": self.cost_center, - "posting_date": self.posting_date, - } - if self.adjustment_type == "Credit Adjustment": - loan_entry["credit"] = self.amount - loan_entry["credit_in_account_currency"] = self.amount + loan_entry = { + "account": loan_account, + "against": self.adjustment_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _(remarks), + "cost_center": self.cost_center, + "party_type": self.applicant_type, + "party": self.applicant, + "posting_date": self.posting_date, + } + company_entry = { + "account": self.adjustment_account, + "against": loan_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _(remarks), + "cost_center": self.cost_center, + "posting_date": self.posting_date, + } + if self.adjustment_type == "Credit Adjustment": + loan_entry["credit"] = self.amount + loan_entry["credit_in_account_currency"] = self.amount - company_entry["debit"] = self.amount - company_entry["debit_in_account_currency"] = self.amount + company_entry["debit"] = self.amount + company_entry["debit_in_account_currency"] = self.amount - elif self.adjustment_type == "Debit Adjustment": - loan_entry["debit"] = self.amount - loan_entry["debit_in_account_currency"] = self.amount + elif self.adjustment_type == "Debit Adjustment": + loan_entry["debit"] = self.amount + loan_entry["debit_in_account_currency"] = self.amount - company_entry["credit"] = self.amount - company_entry["credit_in_account_currency"] = self.amount + company_entry["credit"] = self.amount + company_entry["credit_in_account_currency"] = self.amount - gle_map.append(self.get_gl_dict(loan_entry)) + gle_map.append(self.get_gl_dict(loan_entry)) - gle_map.append(self.get_gl_dict(company_entry)) + gle_map.append(self.get_gl_dict(company_entry)) - if gle_map: - make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) + if gle_map: + make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) From 0ed6382ab6e3250801489fcb2c87e281c97143cb Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 7 Jul 2022 17:40:50 +0530 Subject: [PATCH 83/98] fix indent and imports --- .../loan_balance_adjustment.py | 130 +++++++++--------- .../doctype/loan_refund/loan_refund.py | 14 +- 2 files changed, 71 insertions(+), 73 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index 92e9b6acde..e714545a31 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -2,14 +2,15 @@ # For license information, please see license.txt import frappe -import erpnext from frappe import _ -from frappe.utils import nowdate, add_days +from frappe.utils import add_days, nowdate + +import erpnext +from erpnext.accounts.general_ledger import make_gl_entries from erpnext.controllers.accounts_controller import AccountsController from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( - process_loan_interest_accrual_for_demand_loans, + process_loan_interest_accrual_for_demand_loans, ) -from erpnext.accounts.general_ledger import make_gl_entries class LoanBalanceAdjustment(AccountsController): @@ -19,9 +20,9 @@ class LoanBalanceAdjustment(AccountsController): def validate(self): if self.amount == 0: - frappe.throw(_("Amount cannot be zero")) + frappe.throw(_("Amount cannot be zero")) if self.amount < 0: - frappe.throw(_("Amount cannot be negative")) + frappe.throw(_("Amount cannot be negative")) self.set_missing_values() def on_submit(self): @@ -35,113 +36,106 @@ class LoanBalanceAdjustment(AccountsController): def set_missing_values(self): if not self.posting_date: - self.posting_date = nowdate() + self.posting_date = nowdate() if not self.cost_center: - self.cost_center = erpnext.get_default_cost_center(self.company) + self.cost_center = erpnext.get_default_cost_center(self.company) def set_status_and_amounts(self, cancel=0): loan_details = frappe.get_all( - "Loan", - fields=[ - "loan_amount", - "adjustment_amount", - "total_payment", - "total_principal_paid", - "total_interest_payable", - "status", - "is_term_loan", - "is_secured_loan", - ], - filters={"name": self.loan}, + "Loan", + fields=[ + "loan_amount", + "adjustment_amount", + "total_payment", + "total_principal_paid", + "total_interest_payable", + "status", + "is_term_loan", + "is_secured_loan", + ], + filters={"name": self.loan}, )[0] if cancel: - adjustment_amount = self.get_values_on_cancel(loan_details) + adjustment_amount = self.get_values_on_cancel(loan_details) else: - adjustment_amount = self.get_values_on_submit(loan_details) + adjustment_amount = self.get_values_on_submit(loan_details) if self.adjustment_type == "Credit Adjustment": - adj_field = "credit_adjustment_amount" + adj_field = "credit_adjustment_amount" elif self.adjustment_type == "Debit Adjustment": - adj_field = "debit_adjustment_amount" + adj_field = "debit_adjustment_amount" - frappe.db.set_value( - "Loan", self.loan, {adj_field: adjustment_amount} - ) + frappe.db.set_value("Loan", self.loan, {adj_field: adjustment_amount}) def get_values_on_cancel(self, loan_details): if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.credit_adjustment_amount - self.amount + adjustment_amount = loan_details.credit_adjustment_amount - self.amount elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.debit_adjustment_amount - self.amount + adjustment_amount = loan_details.debit_adjustment_amount - self.amount return adjustment_amount def get_values_on_submit(self, loan_details): if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.credit_adjustment_amount + self.amount + adjustment_amount = loan_details.credit_adjustment_amount + self.amount elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.debit_adjustment_amount + self.amount + adjustment_amount = loan_details.debit_adjustment_amount + self.amount - if ( - loan_details.status in ("Disbursed", "Partially Disbursed") - and not loan_details.is_term_loan - ): - process_loan_interest_accrual_for_demand_loans( - posting_date=add_days(self.posting_date, -1), - loan=self.loan, - accrual_type=self.adjustment_type, - ) + if loan_details.status in ("Disbursed", "Partially Disbursed") and not loan_details.is_term_loan: + process_loan_interest_accrual_for_demand_loans( + posting_date=add_days(self.posting_date, -1), + loan=self.loan, + accrual_type=self.adjustment_type, + ) return adjustment_amount def make_gl_entries(self, cancel=0, adv_adj=0): gle_map = [] loan_account = frappe.db.get_value("Loan", self.loan, "loan_account") - remarks = "{} against loan {}".format( - self.adjustment_type.capitalize(), self.loan - ) + remarks = "{} against loan {}".format(self.adjustment_type.capitalize(), self.loan) if self.reference_number: remarks += "with reference no. {}".format(self.reference_number) loan_entry = { - "account": loan_account, - "against": self.adjustment_account, - "against_voucher_type": "Loan", - "against_voucher": self.loan, - "remarks": _(remarks), - "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, - "posting_date": self.posting_date, + "account": loan_account, + "against": self.adjustment_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _(remarks), + "cost_center": self.cost_center, + "party_type": self.applicant_type, + "party": self.applicant, + "posting_date": self.posting_date, } company_entry = { - "account": self.adjustment_account, - "against": loan_account, - "against_voucher_type": "Loan", - "against_voucher": self.loan, - "remarks": _(remarks), - "cost_center": self.cost_center, - "posting_date": self.posting_date, + "account": self.adjustment_account, + "against": loan_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _(remarks), + "cost_center": self.cost_center, + "posting_date": self.posting_date, } if self.adjustment_type == "Credit Adjustment": - loan_entry["credit"] = self.amount - loan_entry["credit_in_account_currency"] = self.amount + loan_entry["credit"] = self.amount + loan_entry["credit_in_account_currency"] = self.amount - company_entry["debit"] = self.amount - company_entry["debit_in_account_currency"] = self.amount + company_entry["debit"] = self.amount + company_entry["debit_in_account_currency"] = self.amount elif self.adjustment_type == "Debit Adjustment": - loan_entry["debit"] = self.amount - loan_entry["debit_in_account_currency"] = self.amount + loan_entry["debit"] = self.amount + loan_entry["debit_in_account_currency"] = self.amount - company_entry["credit"] = self.amount - company_entry["credit_in_account_currency"] = self.amount + company_entry["credit"] = self.amount + company_entry["credit_in_account_currency"] = self.amount gle_map.append(self.get_gl_dict(loan_entry)) gle_map.append(self.get_gl_dict(company_entry)) if gle_map: - make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) + make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py index 2a7f47871f..f63cb27512 100644 --- a/erpnext/loan_management/doctype/loan_refund/loan_refund.py +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -1,13 +1,20 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe +from frappe import _ from frappe.model.document import Document +from frappe.utils import cint, flt, getdate + +import erpnext +from erpnext.accounts.general_ledger import make_gl_entries + class LoanRefund(Document): """ Add refund if total repayment is more than that is owed. """ + def validate(self): self.set_missing_values() self.validate_refund_amount() @@ -30,10 +37,7 @@ class LoanRefund(Document): ) if self.refund_amount > excess_amount: - frappe.throw(_( - "Refund amount cannot be greater than excess amount {}".format( - excess_amount - ))) + frappe.throw(_("Refund amount cannot be greater than excess amount {}".format(excess_amount))) def on_submit(self): self.update_outstanding_amount() From 57b6dab1da2ad7979f17c10379c54f212fac9d7e Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 7 Jul 2022 23:19:17 +0530 Subject: [PATCH 84/98] fix: status filter in Job Card Summary --- .../manufacturing/report/job_card_summary/job_card_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index a86c7a47c3..5083b7369d 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -36,7 +36,7 @@ def get_data(filters): "total_time_in_mins", ] - for field in ["work_order", "workstation", "operation", "company"]: + for field in ["work_order", "workstation", "operation", "status", "company"]: if filters.get(field): query_filters[field] = ("in", filters.get(field)) From 300e812a1f2b8752a66c009560ec99e34a07abd1 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 8 Jul 2022 15:38:44 +0530 Subject: [PATCH 85/98] chore: Instantiate variable unconditionally --- .../doctype/request_for_quotation/request_for_quotation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 2fda831d75..f319506ff9 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -181,6 +181,7 @@ class RequestforQuotation(BuyingController): doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")}) # Get Contact Full Name + supplier_name = None if data.get("contact"): contact_name = frappe.db.get_value( "Contact", data.get("contact"), ["first_name", "middle_name", "last_name"] From 6cc09ef3a203724c73d4fa583cf4b7720dacdd65 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 8 Jul 2022 16:38:41 +0530 Subject: [PATCH 86/98] fix adjustment amount field name --- .../doctype/loan_balance_adjustment/loan_balance_adjustment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index e714545a31..2b5b1f7426 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -46,7 +46,8 @@ class LoanBalanceAdjustment(AccountsController): "Loan", fields=[ "loan_amount", - "adjustment_amount", + "credit_adjustment_amount", + "debit_adjustment_amount", "total_payment", "total_principal_paid", "total_interest_payable", From 245b0c7818587b21874cdc5ddadd6eac787beeb2 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 8 Jul 2022 16:49:19 +0530 Subject: [PATCH 87/98] Update adjustment_type field options --- .../loan_balance_adjustment/loan_balance_adjustment.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json index cfbde0f6ce..80c3389ba1 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json @@ -132,7 +132,7 @@ "fieldname": "adjustment_type", "fieldtype": "Select", "label": "Adjustment Type", - "options": "Credit\nDebit", + "options": "Credit Adjustment\nDebit Adjustment", "reqd": 1 }, { @@ -144,7 +144,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-06-30 11:38:16.631994", + "modified": "2022-07-08 16:48:54.480066", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Balance Adjustment", From 9df1413adb94ec59cae19e3cdf1afe62ce8b5ce6 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 8 Jul 2022 17:52:36 +0530 Subject: [PATCH 88/98] fix excess amount calculation in loan refund --- .../doctype/loan_refund/loan_refund.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py index f63cb27512..766cdb6f04 100644 --- a/erpnext/loan_management/doctype/loan_refund/loan_refund.py +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -4,10 +4,13 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, flt, getdate +from frappe.utils import cint, getdate import erpnext from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.loan_management.doctype.loan_repayment.loan_repayment import ( + get_pending_principal_amount, +) class LoanRefund(Document): @@ -31,10 +34,12 @@ class LoanRefund(Document): ["total_payment", "total_principal_paid", "total_interest_payable", "written_off_amount"], ) - excess_amount = flt( - flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount), - precision, - ) + loan = frappe.get_doc("Loan", self.loan) + pending_amount = get_pending_principal_amount(loan) + if pending_amount >= 0: + frappe.throw(_("No excess amount to refund.")) + else: + excess_amount = pending_amount * -1 if self.refund_amount > excess_amount: frappe.throw(_("Refund amount cannot be greater than excess amount {}".format(excess_amount))) From 35f2717ad2d1e5c8b40455d3c58636fd6f51c5e0 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 8 Jul 2022 17:53:29 +0530 Subject: [PATCH 89/98] Consider refund_amount in pending principal amount --- .../loan_management/doctype/loan_repayment/loan_repayment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 7b0028deb4..e7d6ca8d70 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -396,7 +396,7 @@ class LoanRepayment(AccountsController): ) else: remarks = "Repayment against loan " + self.against_loan - + if self.reference_number: remarks += "with reference no. {}".format(self.reference_number) @@ -632,6 +632,7 @@ def get_pending_principal_amount(loan): - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) + + flt(loan.refund_amount) ) else: pending_principal_amount = ( @@ -641,6 +642,7 @@ def get_pending_principal_amount(loan): - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) + + flt(loan.refund_amount) ) return pending_principal_amount From 13b7ed1e2c3f22155d59026ebc23d72bc7cbd263 Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Fri, 8 Jul 2022 17:58:36 +0530 Subject: [PATCH 90/98] fix: on cancel for loan refund --- erpnext/loan_management/doctype/loan_refund/loan_refund.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py index 766cdb6f04..903494c5ad 100644 --- a/erpnext/loan_management/doctype/loan_refund/loan_refund.py +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -3,7 +3,7 @@ import frappe from frappe import _ -from frappe.model.document import Document +from erpnext.controllers.accounts_controller import AccountsController from frappe.utils import cint, getdate import erpnext @@ -13,7 +13,7 @@ from erpnext.loan_management.doctype.loan_repayment.loan_repayment import ( ) -class LoanRefund(Document): +class LoanRefund(AccountsController): """ Add refund if total repayment is more than that is owed. """ @@ -50,7 +50,7 @@ class LoanRefund(Document): def on_cancel(self): self.update_outstanding_amount(cancel=1) - self.ignore_linked_doctypes = ["GL Entry"] + self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] self.make_gl_entries(cancel=1) def update_outstanding_amount(self, cancel=0): From 4ec85cb2f0715528fb4888846fc9c27b2d98347f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 8 Jul 2022 19:28:03 +0530 Subject: [PATCH 91/98] test: don't silently fail reposts in tests (#31559) --- .../doctype/repost_item_valuation/repost_item_valuation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index b1017d2c9c..7c57ecd73e 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -138,6 +138,11 @@ def repost(doc): doc.set_status("Completed") except Exception as e: + if frappe.flags.in_test: + # Don't silently fail in tests, + # there is no reason for reposts to fail in CI + raise + frappe.db.rollback() traceback = frappe.get_traceback() doc.log_error("Unable to repost item valuation") From 30315923682949dd3417245ed678e412f9476913 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 11 Jul 2022 11:48:12 +0530 Subject: [PATCH 92/98] chore: form tour location closes https://github.com/frappe/frappe/issues/17247 [skip ci] --- .../form_tour/selling_settings/selling_settings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/form_tour/selling_settings/selling_settings.json b/erpnext/selling/form_tour/selling_settings/selling_settings.json index 20c718f8c0..8d5cf88eba 100644 --- a/erpnext/selling/form_tour/selling_settings/selling_settings.json +++ b/erpnext/selling/form_tour/selling_settings/selling_settings.json @@ -4,7 +4,7 @@ "doctype": "Form Tour", "idx": 0, "is_standard": 1, - "modified": "2021-06-29 20:49:01.359489", + "modified": "2022-07-11 20:49:01.359489", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", @@ -33,7 +33,7 @@ "is_table_field": 0, "label": "Is Sales Order Required for Sales Invoice & Delivery Note Creation?", "parent_field": "", - "position": "Left", + "position": "Right", "title": "Sales Order Required for Sales Invoice & Delivery Note Creation" }, { @@ -45,7 +45,7 @@ "is_table_field": 0, "label": "Is Delivery Note Required for Sales Invoice Creation?", "parent_field": "", - "position": "Left", + "position": "Right", "title": "Delivery Note Required for Sales Invoice Creation" }, { From c83fbd5c50da1e2da9ff5ce6f33a171a11e896a0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Jul 2022 13:43:01 +0530 Subject: [PATCH 93/98] fix: Allow multi currency invoice against single party account --- .../doctype/accounts_settings/accounts_settings.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 417611fecd..3e0b82c561 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -18,6 +18,7 @@ "automatically_fetch_payment_terms", "column_break_17", "enable_common_party_accounting", + "allow_multi_currency_invoices_against_single_party_account", "report_setting_section", "use_custom_cash_flow", "deferred_accounting_settings_section", @@ -339,6 +340,13 @@ "fieldname": "report_setting_section", "fieldtype": "Section Break", "label": "Report Setting" + }, + { + "default": "0", + "description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency", + "fieldname": "allow_multi_currency_invoices_against_single_party_account", + "fieldtype": "Check", + "label": "Allow multi-currency invoices against single party account " } ], "icon": "icon-cog", @@ -346,7 +354,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-04-08 14:45:06.796418", + "modified": "2022-07-11 13:37:50.605141", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 3cf609fab18c532bb8c404a9024b802d82d7d7fe Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Jul 2022 13:46:59 +0530 Subject: [PATCH 94/98] chore: Ignore validation --- erpnext/controllers/accounts_controller.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ceac815bf4..19875faacb 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1472,8 +1472,15 @@ class AccountsController(TransactionBase): self.get("debit_to") if self.doctype == "Sales Invoice" else self.get("credit_to") ) party_account_currency = get_account_currency(party_account) + allow_multi_currency_invoices_against_single_party_account = frappe.get_single_value( + "Accounts Settings", "allow_multi_currency_invoices_against_single_party_account" + ) - if not party_gle_currency and (party_account_currency != self.currency): + if ( + not party_gle_currency + and (party_account_currency != self.currency) + and not allow_multi_currency_invoices_against_single_party_account + ): frappe.throw( _("Party Account {0} currency ({1}) and document currency ({2}) should be same").format( frappe.bold(party_account), party_account_currency, self.currency From e04e67c6bf067128d5a5a46e3d6e6fb93531c8b8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Jul 2022 19:25:18 +0530 Subject: [PATCH 95/98] chore: fix query --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 19875faacb..df80ef5bfb 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1472,7 +1472,7 @@ class AccountsController(TransactionBase): self.get("debit_to") if self.doctype == "Sales Invoice" else self.get("credit_to") ) party_account_currency = get_account_currency(party_account) - allow_multi_currency_invoices_against_single_party_account = frappe.get_single_value( + allow_multi_currency_invoices_against_single_party_account = frappe.db.get_singles_value( "Accounts Settings", "allow_multi_currency_invoices_against_single_party_account" ) From d933ff5cf6c9bb7146f38b2c43b28b6edc48b8b0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Jul 2022 21:50:01 +0530 Subject: [PATCH 96/98] chore: Linting Issues --- .../loan_balance_adjustment.py | 9 +++++---- .../doctype/loan_refund/loan_refund.py | 11 ++--------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index 2b5b1f7426..0a576d6969 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -42,9 +42,10 @@ class LoanBalanceAdjustment(AccountsController): self.cost_center = erpnext.get_default_cost_center(self.company) def set_status_and_amounts(self, cancel=0): - loan_details = frappe.get_all( + loan_details = frappe.db.get_value( "Loan", - fields=[ + self.loan, + [ "loan_amount", "credit_adjustment_amount", "debit_adjustment_amount", @@ -55,8 +56,8 @@ class LoanBalanceAdjustment(AccountsController): "is_term_loan", "is_secured_loan", ], - filters={"name": self.loan}, - )[0] + as_dict=1, + ) if cancel: adjustment_amount = self.get_values_on_cancel(loan_details) diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py index 903494c5ad..265c969211 100644 --- a/erpnext/loan_management/doctype/loan_refund/loan_refund.py +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -3,11 +3,11 @@ import frappe from frappe import _ -from erpnext.controllers.accounts_controller import AccountsController -from frappe.utils import cint, getdate +from frappe.utils import getdate import erpnext from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.controllers.accounts_controller import AccountsController from erpnext.loan_management.doctype.loan_repayment.loan_repayment import ( get_pending_principal_amount, ) @@ -27,13 +27,6 @@ class LoanRefund(AccountsController): self.cost_center = erpnext.get_default_cost_center(self.company) def validate_refund_amount(self): - precision = cint(frappe.db.get_default("currency_precision")) or 2 - total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value( - "Loan", - self.loan, - ["total_payment", "total_principal_paid", "total_interest_payable", "written_off_amount"], - ) - loan = frappe.get_doc("Loan", self.loan) pending_amount = get_pending_principal_amount(loan) if pending_amount >= 0: From 0bac030ca7b798e0c87d0f71318f22a1bf6563e3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Jul 2022 22:04:52 +0530 Subject: [PATCH 97/98] chore: Linting Issues --- erpnext/loan_management/doctype/loan_refund/loan_refund.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py index 265c969211..d7ab54ca97 100644 --- a/erpnext/loan_management/doctype/loan_refund/loan_refund.py +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -35,7 +35,7 @@ class LoanRefund(AccountsController): excess_amount = pending_amount * -1 if self.refund_amount > excess_amount: - frappe.throw(_("Refund amount cannot be greater than excess amount {}".format(excess_amount))) + frappe.throw(_("Refund amount cannot be greater than excess amount {0}").format(excess_amount)) def on_submit(self): self.update_outstanding_amount() From 2224b6503b9739fdf4f8f1011a18504fbae772f6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 12 Jul 2022 12:39:22 +0530 Subject: [PATCH 98/98] chore: typo [skip ci] --- erpnext/stock/spec/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/spec/README.md b/erpnext/stock/spec/README.md index f5a3501fe4..5aa1297334 100644 --- a/erpnext/stock/spec/README.md +++ b/erpnext/stock/spec/README.md @@ -7,7 +7,7 @@ - `controllers/stock_controller.py` - `stock/valuation.py` -## What is in an Stock Ledger Entry (SLE)? +## What is in a Stock Ledger Entry (SLE)? Stock Ledger Entry is a single row in the Stock Ledger. It signifies some modification of stock for a particular Item in the specified warehouse.