-- =====================================================================
-- 30_quotation.sql
-- Quotation
--
-- GENERATED by _gen/pack.py from ../step/. DO NOT EDIT -- edit the source
-- there and re-pack, or the sha256 in MANIFEST.md stops matching.
--
-- 5 table(s), 21 ledger step(s).
--
-- ⚠ IMPORT ORDER: 01_system.sql, then all seven 1x masters, then this file. Order among
--    transactions does not matter. Re-importing costs nothing.
--
-- ⚠ STOP ON THE FIRST ERROR. Do not use `mysql --force`, and do not tick any
--    "continue on error" box. Each step records itself in `_migration_ledger`
--    immediately after its statement runs, WITHOUT checking that the statement
--    succeeded -- that is inherited from step/, where run.sh guarantees a stop by
--    running mysql without --force. Continue past an error here and a step that
--    failed is marked applied: 99_finalize.sql will then seed `migrations`, and
--    `php artisan migrate` will skip the work still owed. Both default clients
--    stop on error; the danger is turning that off.
--
-- ⚠ phpMyAdmin: UNTICK "Allow the interruption of an import".
--    If it fires, the import resumes on a NEW CONNECTION: SQL_MODE reverts to the server
--    default and every @variable dies, mid-file. Two things then break. NO_ZERO_DATE is
--    back, so any ALTER that rebuilds a table holding '0000-00-00' fails with ERROR 1292.
--    And @step / @todo / @sql are NULL, so the guard around the next payload stops meaning
--    anything. The SET SQL_MODE before every unit below limits the damage when someone
--    forgets the checkbox; it does not replace unticking it.
--
--    (STRICT_TRANS_TABLES is forced for a third case -- a value too big for its new column
--    is CLAMPED silently rather than refused. Measured on the tree as it stands, no column
--    narrows: all 674 MODIFY COLUMNs widen or are no-ops. It stays forced because the next
--    regeneration is under no obligation to keep that true.)
--
-- ⚠ Take a full backup first. The `20_cleanup` steps change data and nothing here
--    reverses them. `../audit_bukanmain_before_migrate.sql` only reads.
-- =====================================================================

-- Menus this file serves (18 links):
--   /quotations
--   /quotations/approval-pm
--   /quotations/approval-sm
--   /quotations/cancel
--   /quotations/change-status
--   /quotations/create
--   /quotations/create-for-others
--   /quotations/feedback
--   /quotations/good-receive
--   /quotations/process-to-order
--   /quotations/view-all
--   /quotations/view-details
--   /quotations/view-details-cs
--   /quotations/view-details-head-dept
--   /quotations/view-details-mm
--   /quotations/view-details-pm
--   /quotations/view-details-sm
--   /quotations/view-head

-- --------------------------------------------------------------- prologue
-- run.sh sources step/_lib/session.sql for you. phpMyAdmin will not, so it is inlined.
             AND @@SQL_MODE LIKE '%NO_AUTO_VALUE_ON_ZERO%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_DATE%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_IN_DATE%',
        @@SQL_MODE,
        CONCAT(REPLACE(REPLACE(@@SQL_MODE, 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', ''),
               ',NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES'));
-- Assert all four, not just the first. STRICT_TRANS_TABLES missing means out-of-range
-- values are clamped silently; NO_ZERO_DATE still present means any ALTER that rebuilds a
-- legacy table re-validates its '0000-00-00' rows and dies with ERROR 1292. The second is
-- the one that actually stops the migration, and it was not being checked.
SELECT IF(@@SQL_MODE LIKE '%STRICT_TRANS_TABLES%'
      AND @@SQL_MODE LIKE '%NO_AUTO_VALUE_ON_ZERO%'
      AND @@SQL_MODE NOT LIKE '%NO_ZERO_DATE%'
      AND @@SQL_MODE NOT LIKE '%NO_ZERO_IN_DATE%',
          'sql_mode OK - proceeding',
          (SELECT 'ABORT: sql_mode is wrong. Wanted STRICT_TRANS_TABLES + NO_AUTO_VALUE_ON_ZERO,'
           UNION ALL
           SELECT 'and NO_ZERO_DATE / NO_ZERO_IN_DATE OFF. The SET above should have done it.')
       ) AS preflight_sql_mode;

SET SESSION lock_wait_timeout = 60;
SET SESSION innodb_lock_wait_timeout = 60;

-- The ledger, copied verbatim from step/_lib/ledger.sql -- not restated, so the two
-- paths cannot disagree about its definition.
-- ---------------------------------------------------------------------
-- _lib/ledger.sql  --  the only thing here that is not in the source file
-- One row per applied step. This is the record that decides what a re-run skips,
-- what --status reports, and -- through the app-side gate -- which modules the
-- application will serve. It lives in the database on purpose: a file on one
-- operator's laptop drifts the moment someone runs a step from another machine.
--
-- Deliberately NOT in the project's PascalCase convention. It is an ops table, never
-- touched by Eloquent, and it should be visibly not-an-app-table.
--
-- `checksum` is the SHA-256 of the step's SQL payload -- the verbatim statements,
-- not the file -- so it cannot change when a comment above them is reworded.
-- _lib/steps.tsv carries the whole-file hashes for tamper detection.
--
-- ⚠ `step` IS ascii, AND THAT IS LOAD-BEARING. Every guard compares it against a
-- user variable, and a user variable carries the CONNECTION collation. Give this
-- column utf8mb4_unicode_ci and the very first step dies with ERROR 1267,
-- "Illegal mix of collations (utf8mb4_unicode_ci,IMPLICIT) and
-- (utf8mb4_0900_ai_ci,IMPLICIT) for operation '='" -- measured, that is exactly
-- how the first end-to-end run failed. ascii is a repertoire subset of latin1 and
-- of utf8mb4, so the server coerces it to whatever the connection is using,
-- whatever that turns out to be on the day. Step names are [a-z0-9_/] by
-- construction, so nothing is lost.
-- ---------------------------------------------------------------------

CREATE TABLE IF NOT EXISTS `_migration_ledger` (
  `step`        varchar(191) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
  `applied_at`  datetime     NOT NULL,
  `checksum`    char(64)     NOT NULL,
  `duration_ms` int          NOT NULL,
  PRIMARY KEY (`step`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


-- --------------------------------------------------------------- preflight
-- 00_system/01_system.sql MUST already be applied. Every doc says so; until now nothing
-- enforced it, and the violation was silent for most of the tree: only three files break
-- without it (30_quotation and 40_budgetandtarget hit ERROR 1146 on a table 01_system
-- creates, and 99_finalize needs `migrations`). The other twenty import cleanly, record
-- their ledger rows, and leave a database that is half-migrated with nothing to say so.
--
-- 13 steps: the ALTER DATABASE plus the 12 CREATE TABLEs.
SET @sys_done = (SELECT COUNT(*) FROM `_migration_ledger` WHERE `step` LIKE '\_system/%');
SET @pf_sys = IF(@sys_done >= 13,
    'SELECT ''preflight: 00_system is applied'' AS preflight_system',
    'SELECT `ABORT: import 00_system/01_system.sql first. It runs the ALTER DATABASE and creates the 12 new tables that everything here assumes exist.`');
PREPARE _pf FROM @pf_sys; EXECUTE _pf; DEALLOCATE PREPARE _pf;

-- --------------------------------------------------------------- preflight
-- WAS THIS DATABASE MIGRATED FROM A DIFFERENT VERSION OF THIS FILE?
--
-- `_migration_ledger.checksum` is the SHA-256 of each step's payload. It is written 632
-- times across the tree and, until this block existed, read never -- so a corrected payload
-- re-issued under the same step name was skipped forever on any database that had recorded
-- the old one, silently and permanently.
SELECT COUNT(*) INTO @ck_bad FROM `_migration_ledger`
 WHERE `step` IN ('quotation/10_alter', 'quotation/20_cleanup', 'quotation/30_assert', 'quotation/40_fk', 'quotationassignment/10_alter', 'quotationassignment/20_cleanup', 'quotationassignment/30_assert', 'quotationassignment/40_fk', 'quotationdetails/10_alter', 'quotationdetails/15_repair', 'quotationdetails/20_cleanup', 'quotationdetails/30_assert', 'quotationdetails/40_fk', 'quotationdetailsassignment/10_alter', 'quotationdetailsassignment/20_cleanup', 'quotationdetailsassignment/30_assert', 'quotationdetailsassignment/40_fk', 'quotationlink/10_alter', 'quotationlink/20_cleanup', 'quotationlink/30_assert', 'quotationlink/40_fk')
   AND CONCAT(`step`, ':', `checksum`) NOT IN ('quotation/10_alter:c5f761f72731f8a34f47e796999fd3078e203fcfc5dcae9bf0768a486b678e2a', 'quotation/20_cleanup:287edb04730daf8021d7b72972b513575919821bc94e0b5657e777f15cb19907', 'quotation/30_assert:a84a498d4c8e9519cee512eac9546f278a808e4550b5c207f5815faaa8e2f53d', 'quotation/40_fk:46e255bee5dcd01bc131792a31c0d57756ef26c3a90ec85e8ccc00161f154e7f', 'quotationassignment/10_alter:794978597789479ca3c37cdb6546ec3e5908258da4684c06d695a9e6494570b7', 'quotationassignment/20_cleanup:7faa193b15de79179380a0f819451789d2da9b1302f1af021cc9ac9f0ce5acac', 'quotationassignment/30_assert:b9045cdc08b099a5bae7132c0f1e0d6c91c22eda20903ed2391767a0e72fbb1b', 'quotationassignment/40_fk:3d2f3aac6d94bc6db8d62b7a7476b7e1273d21cf196da9fbd1cd253eca61e6fc', 'quotationdetails/10_alter:5d544291a14c791f2f55a60152a99a46e8d8b1b6881c34a659636bcb7b98b43a', 'quotationdetails/15_repair:3c40f473fb3747f5b32954dd121cbc3525750ab44654637be7c9b1550be142ac', 'quotationdetails/20_cleanup:4e8e6d15a4d8e6f4699be9b207de5eaeb05dc9963b03f005c445448dc08daa3d', 'quotationdetails/30_assert:87d323c3ac0e565c710296ffb5f94f00e81ba9985c8db9c74a4e33a6b3054f44', 'quotationdetails/40_fk:80380d27173359a4e4f683a50b63529bc9e25b18f6efea7e6fb5e56e9ce695bb', 'quotationdetailsassignment/10_alter:d5c2d8bf68e22967272acc6bcd6b989c983a975040f1b02cf88439c470204dff', 'quotationdetailsassignment/20_cleanup:f8f88cf4b5429acd1b9801a3c054b1f615b5550999bff46efe848bf0bb00058f', 'quotationdetailsassignment/30_assert:7c0ea646dd5641dccc139fec726ecf30cf863a56d37e4432bd3fe517b414391f', 'quotationdetailsassignment/40_fk:075a8c5198e43c14b4e42faf9b7814b1e09fb81672baba8d4d02484004b4e8fb', 'quotationlink/10_alter:1cc3eb00acde568afc61650e423055c60ac6a26ce50716c38235b509c528b73b', 'quotationlink/20_cleanup:9c6d6314fb346915eb68dc91f196059d3f9a9314db1d8b00db58decbd0cd388e', 'quotationlink/30_assert:4cca5cb0982a60c2cdd59f33665af0b2f07a791b3ce5347de641550a6070e53e', 'quotationlink/40_fk:c900e0d9381623bbc129b938a0ad3903de551a3b32ffab5f58f2e33f3f162141');
SET @pf_ck = IF(@ck_bad = 0,
    'SELECT ''preflight: ledger matches this file'' AS preflight_checksum',
    CONCAT('SELECT `ABORT: ', @ck_bad, ' step(s) here were applied from a DIFFERENT version of this file. Their payloads changed. Compare before re-importing; do not delete ledger rows blindly.`'));
PREPARE _pf FROM @pf_ck; EXECUTE _pf; DEALLOCATE PREPARE _pf;

-- ---------------------------------------------------------------------
-- unit: quotation    (transaction)
-- ---------------------------------------------------------------------
             AND @@SQL_MODE LIKE '%NO_AUTO_VALUE_ON_ZERO%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_DATE%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_IN_DATE%',
        @@SQL_MODE,
        CONCAT(REPLACE(REPLACE(@@SQL_MODE, 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', ''),
               ',NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES'));
-- ---------------------------------------------------------------------
-- quotation/10_alter  --  source section A
-- The merged ALTER: new columns, type/charset changes, column order, NULL relaxation
-- and the indexes every foreign key needs -- ONE statement, so InnoDB rebuilds this
-- table ONCE. The original migration split these across five sections and rebuilt some
-- tables four times; that is where its 12 hours went. Do not split this back up.
--
-- Clause order is the original section order (3b, 3d, 3e, 4, 7), which is what keeps
-- every ADD COLUMN ... AFTER positioned exactly as before.
-- ---------------------------------------------------------------------

SET @step = 'quotation/10_alter';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'ALTER TABLE `quotation`
  MODIFY COLUMN `IndustryID` varchar(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL,
  MODIFY COLUMN `QuotationStatusID` int DEFAULT NULL,
  MODIFY COLUMN `ReferenceQuotationID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationSubjectID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationTypeID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationTitleID` int DEFAULT NULL,
  MODIFY COLUMN `DivisionID` int DEFAULT NULL,
  MODIFY COLUMN `CompanyID` int DEFAULT NULL,
  MODIFY COLUMN `CompanyCPID` int DEFAULT NULL,
  MODIFY COLUMN `DeliveryAddressID` int DEFAULT NULL,
  MODIFY COLUMN `InvoiceAddressID` int DEFAULT NULL,
  MODIFY COLUMN `UserIDInput` int DEFAULT NULL,
  MODIFY COLUMN `UserIDSales` int DEFAULT NULL,
  MODIFY COLUMN `QuotationPriceDescID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationSalesTermID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationPaymentTermID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationDeliveryTimeID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationValidityID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationStockAvailibilityID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationFeedbackStatusID` int DEFAULT NULL,
  ADD INDEX `quotation_CompanyCPID` (`CompanyCPID`),
  ADD INDEX `quotation_CompanyID` (`CompanyID`),
  ADD INDEX `quotation_DeliveryAddressID` (`DeliveryAddressID`),
  ADD INDEX `quotation_DivisionID` (`DivisionID`),
  ADD INDEX `quotation_InvoiceAddressID` (`InvoiceAddressID`),
  ADD INDEX `quotation_QuotationDeliveryTimeID` (`QuotationDeliveryTimeID`),
  ADD INDEX `quotation_QuotationFeedbackStatusID` (`QuotationFeedbackStatusID`),
  ADD INDEX `quotation_QuotationPaymentTermID` (`QuotationPaymentTermID`),
  ADD INDEX `quotation_QuotationPriceDescID` (`QuotationPriceDescID`),
  ADD INDEX `quotation_QuotationSalesTermID` (`QuotationSalesTermID`),
  ADD INDEX `quotation_QuotationStatusID` (`QuotationStatusID`),
  ADD INDEX `quotation_QuotationStockAvailibilityID` (`QuotationStockAvailibilityID`),
  ADD INDEX `quotation_QuotationSubjectID` (`QuotationSubjectID`),
  ADD INDEX `quotation_QuotationTitleID` (`QuotationTitleID`),
  ADD INDEX `quotation_QuotationTypeID` (`QuotationTypeID`),
  ADD INDEX `quotation_QuotationValidityID` (`QuotationValidityID`),
  ADD INDEX `quotation_ReferenceQuotationID` (`ReferenceQuotationID`),
  ADD INDEX `quotation_UserIDInput` (`UserIDInput`),
  ADD INDEX `quotation_UserIDSales` (`UserIDSales`)', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), 'c5f761f72731f8a34f47e796999fd3078e203fcfc5dcae9bf0768a486b678e2a', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;
-- ---------------------------------------------------------------------
-- quotation/20_cleanup  --  source section B (= the original sections 5 and 6, merged)
-- 0-sentinels AND orphaned references -> NULL, one pass per table.
--     section 5:  WHERE `c` = 0
--     section 6:  WHERE `c` IS NOT NULL AND parent.ID IS NULL
--     merged   :  WHERE `c` IS NOT NULL AND (`c` = 0 OR parent.ID IS NULL)
-- Every LEFT JOIN is a primary-key equality, so none of them can multiply rows.
--
-- DATA CHANGE, and this script cannot reverse it. Run the audit first.
-- In its own transaction, with the ledger row inside it: either the cleanup and the
-- record of it both land, or neither does.
-- ---------------------------------------------------------------------

START TRANSACTION;

SET @step = 'quotation/20_cleanup';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'UPDATE `quotation` c
  LEFT JOIN `companycp` p0 ON c.`CompanyCPID` = p0.`ID`
  LEFT JOIN `company` p1 ON c.`CompanyID` = p1.`ID`
  LEFT JOIN `companycp` p2 ON c.`DeliveryAddressID` = p2.`ID`
  LEFT JOIN `division` p3 ON c.`DivisionID` = p3.`ID`
  LEFT JOIN `companycp` p4 ON c.`InvoiceAddressID` = p4.`ID`
  LEFT JOIN `quotationdeliverytime` p5 ON c.`QuotationDeliveryTimeID` = p5.`ID`
  LEFT JOIN `quotationfeedbackstatus` p6 ON c.`QuotationFeedbackStatusID` = p6.`ID`
  LEFT JOIN `quotationpaymentterm` p7 ON c.`QuotationPaymentTermID` = p7.`ID`
  LEFT JOIN `quotationpricedesc` p8 ON c.`QuotationPriceDescID` = p8.`ID`
  LEFT JOIN `quotationsalesterm` p9 ON c.`QuotationSalesTermID` = p9.`ID`
  LEFT JOIN `quotationstatus` p10 ON c.`QuotationStatusID` = p10.`ID`
  LEFT JOIN `quotationstockavailability` p11 ON c.`QuotationStockAvailibilityID` = p11.`ID`
   SET
       c.`CompanyCPID` = IF((c.`CompanyCPID` IS NOT NULL AND (c.`CompanyCPID` = 0 OR p0.`ID` IS NULL)), NULL, c.`CompanyCPID`),
       c.`CompanyID` = IF((c.`CompanyID` IS NOT NULL AND (c.`CompanyID` = 0 OR p1.`ID` IS NULL)), NULL, c.`CompanyID`),
       c.`DeliveryAddressID` = IF((c.`DeliveryAddressID` IS NOT NULL AND (c.`DeliveryAddressID` = 0 OR p2.`ID` IS NULL)), NULL, c.`DeliveryAddressID`),
       c.`DivisionID` = IF((c.`DivisionID` IS NOT NULL AND (c.`DivisionID` = 0 OR p3.`ID` IS NULL)), NULL, c.`DivisionID`),
       c.`InvoiceAddressID` = IF((c.`InvoiceAddressID` IS NOT NULL AND (c.`InvoiceAddressID` = 0 OR p4.`ID` IS NULL)), NULL, c.`InvoiceAddressID`),
       c.`QuotationDeliveryTimeID` = IF((c.`QuotationDeliveryTimeID` IS NOT NULL AND (c.`QuotationDeliveryTimeID` = 0 OR p5.`ID` IS NULL)), NULL, c.`QuotationDeliveryTimeID`),
       c.`QuotationFeedbackStatusID` = IF((c.`QuotationFeedbackStatusID` IS NOT NULL AND (c.`QuotationFeedbackStatusID` = 0 OR p6.`ID` IS NULL)), NULL, c.`QuotationFeedbackStatusID`),
       c.`QuotationPaymentTermID` = IF((c.`QuotationPaymentTermID` IS NOT NULL AND (c.`QuotationPaymentTermID` = 0 OR p7.`ID` IS NULL)), NULL, c.`QuotationPaymentTermID`),
       c.`QuotationPriceDescID` = IF((c.`QuotationPriceDescID` IS NOT NULL AND (c.`QuotationPriceDescID` = 0 OR p8.`ID` IS NULL)), NULL, c.`QuotationPriceDescID`),
       c.`QuotationSalesTermID` = IF((c.`QuotationSalesTermID` IS NOT NULL AND (c.`QuotationSalesTermID` = 0 OR p9.`ID` IS NULL)), NULL, c.`QuotationSalesTermID`),
       c.`QuotationStatusID` = IF((c.`QuotationStatusID` IS NOT NULL AND (c.`QuotationStatusID` = 0 OR p10.`ID` IS NULL)), NULL, c.`QuotationStatusID`),
       c.`QuotationStockAvailibilityID` = IF((c.`QuotationStockAvailibilityID` IS NOT NULL AND (c.`QuotationStockAvailibilityID` = 0 OR p11.`ID` IS NULL)), NULL, c.`QuotationStockAvailibilityID`)
 WHERE  (c.`CompanyCPID` IS NOT NULL AND (c.`CompanyCPID` = 0 OR p0.`ID` IS NULL))
    OR (c.`CompanyID` IS NOT NULL AND (c.`CompanyID` = 0 OR p1.`ID` IS NULL))
    OR (c.`DeliveryAddressID` IS NOT NULL AND (c.`DeliveryAddressID` = 0 OR p2.`ID` IS NULL))
    OR (c.`DivisionID` IS NOT NULL AND (c.`DivisionID` = 0 OR p3.`ID` IS NULL))
    OR (c.`InvoiceAddressID` IS NOT NULL AND (c.`InvoiceAddressID` = 0 OR p4.`ID` IS NULL))
    OR (c.`QuotationDeliveryTimeID` IS NOT NULL AND (c.`QuotationDeliveryTimeID` = 0 OR p5.`ID` IS NULL))
    OR (c.`QuotationFeedbackStatusID` IS NOT NULL AND (c.`QuotationFeedbackStatusID` = 0 OR p6.`ID` IS NULL))
    OR (c.`QuotationPaymentTermID` IS NOT NULL AND (c.`QuotationPaymentTermID` = 0 OR p7.`ID` IS NULL))
    OR (c.`QuotationPriceDescID` IS NOT NULL AND (c.`QuotationPriceDescID` = 0 OR p8.`ID` IS NULL))
    OR (c.`QuotationSalesTermID` IS NOT NULL AND (c.`QuotationSalesTermID` = 0 OR p9.`ID` IS NULL))
    OR (c.`QuotationStatusID` IS NOT NULL AND (c.`QuotationStatusID` = 0 OR p10.`ID` IS NULL))
    OR (c.`QuotationStockAvailibilityID` IS NOT NULL AND (c.`QuotationStockAvailibilityID` = 0 OR p11.`ID` IS NULL))', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
SET @sql = IF(@todo, 'UPDATE `quotation` c
  LEFT JOIN `quotationsubject` p0 ON c.`QuotationSubjectID` = p0.`ID`
  LEFT JOIN `quotationtitle` p1 ON c.`QuotationTitleID` = p1.`ID`
  LEFT JOIN `quotationtype` p2 ON c.`QuotationTypeID` = p2.`ID`
  LEFT JOIN `quotationvalidity` p3 ON c.`QuotationValidityID` = p3.`ID`
  LEFT JOIN `quotation` p4 ON c.`ReferenceQuotationID` = p4.`ID`
  LEFT JOIN `users` p5 ON c.`UserIDInput` = p5.`ID`
  LEFT JOIN `users` p6 ON c.`UserIDSales` = p6.`ID`
   SET
       c.`QuotationSubjectID` = IF((c.`QuotationSubjectID` IS NOT NULL AND (c.`QuotationSubjectID` = 0 OR p0.`ID` IS NULL)), NULL, c.`QuotationSubjectID`),
       c.`QuotationTitleID` = IF((c.`QuotationTitleID` IS NOT NULL AND (c.`QuotationTitleID` = 0 OR p1.`ID` IS NULL)), NULL, c.`QuotationTitleID`),
       c.`QuotationTypeID` = IF((c.`QuotationTypeID` IS NOT NULL AND (c.`QuotationTypeID` = 0 OR p2.`ID` IS NULL)), NULL, c.`QuotationTypeID`),
       c.`QuotationValidityID` = IF((c.`QuotationValidityID` IS NOT NULL AND (c.`QuotationValidityID` = 0 OR p3.`ID` IS NULL)), NULL, c.`QuotationValidityID`),
       c.`ReferenceQuotationID` = IF((c.`ReferenceQuotationID` IS NOT NULL AND (c.`ReferenceQuotationID` = 0 OR p4.`ID` IS NULL)), NULL, c.`ReferenceQuotationID`),
       c.`UserIDInput` = IF((c.`UserIDInput` IS NOT NULL AND (c.`UserIDInput` = 0 OR p5.`ID` IS NULL)), NULL, c.`UserIDInput`),
       c.`UserIDSales` = IF((c.`UserIDSales` IS NOT NULL AND (c.`UserIDSales` = 0 OR p6.`ID` IS NULL)), NULL, c.`UserIDSales`)
 WHERE  (c.`QuotationSubjectID` IS NOT NULL AND (c.`QuotationSubjectID` = 0 OR p0.`ID` IS NULL))
    OR (c.`QuotationTitleID` IS NOT NULL AND (c.`QuotationTitleID` = 0 OR p1.`ID` IS NULL))
    OR (c.`QuotationTypeID` IS NOT NULL AND (c.`QuotationTypeID` = 0 OR p2.`ID` IS NULL))
    OR (c.`QuotationValidityID` IS NOT NULL AND (c.`QuotationValidityID` = 0 OR p3.`ID` IS NULL))
    OR (c.`ReferenceQuotationID` IS NOT NULL AND (c.`ReferenceQuotationID` = 0 OR p4.`ID` IS NULL))
    OR (c.`UserIDInput` IS NOT NULL AND (c.`UserIDInput` = 0 OR p5.`ID` IS NULL))
    OR (c.`UserIDSales` IS NOT NULL AND (c.`UserIDSales` = 0 OR p6.`ID` IS NULL))', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '287edb04730daf8021d7b72972b513575919821bc94e0b5657e777f15cb19907', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;

COMMIT;
-- ---------------------------------------------------------------------
-- quotation/30_assert  --  source section 7b
-- Orphan assertion over this table's 19 foreign-key relationships.
--
-- This is what pays for 40_fk being fast. The original created the constraints with
-- FOREIGN_KEY_CHECKS ON so a row the cleanup missed would abort the run, and paid for it
-- with a full table copy per constraint. This buys the same protection with one indexed
-- pass, and 40_fk then adds the constraints as metadata only.
--
-- NOT ledger-guarded on the way in, unlike every other phase: it only reads, and the
-- point of it is to be re-runnable. It does record its ledger row -- 40_fk requires it.
--
-- IF THIS ABORTS: a row survived 20_cleanup. Same stop-and-look event the original would
-- have hit as ERROR 1452 inside section 8, but nothing has been created yet.
-- ---------------------------------------------------------------------

SET @step = 'quotation/30_assert';
SET @t0 = NOW(3);
SET @orphans = 0;
SELECT COUNT(*) INTO @n FROM `quotation` c
  LEFT JOIN `companycp` p0 ON c.`CompanyCPID` = p0.`ID`
  LEFT JOIN `company` p1 ON c.`CompanyID` = p1.`ID`
  LEFT JOIN `companycp` p2 ON c.`DeliveryAddressID` = p2.`ID`
  LEFT JOIN `division` p3 ON c.`DivisionID` = p3.`ID`
  LEFT JOIN `companycp` p4 ON c.`InvoiceAddressID` = p4.`ID`
  LEFT JOIN `quotationdeliverytime` p5 ON c.`QuotationDeliveryTimeID` = p5.`ID`
  LEFT JOIN `quotationfeedbackstatus` p6 ON c.`QuotationFeedbackStatusID` = p6.`ID`
  LEFT JOIN `quotationpaymentterm` p7 ON c.`QuotationPaymentTermID` = p7.`ID`
  LEFT JOIN `quotationpricedesc` p8 ON c.`QuotationPriceDescID` = p8.`ID`
  LEFT JOIN `quotationsalesterm` p9 ON c.`QuotationSalesTermID` = p9.`ID`
  LEFT JOIN `quotationstatus` p10 ON c.`QuotationStatusID` = p10.`ID`
  LEFT JOIN `quotationstockavailability` p11 ON c.`QuotationStockAvailibilityID` = p11.`ID`
 WHERE  (c.`CompanyCPID` IS NOT NULL AND p0.`ID` IS NULL)
    OR (c.`CompanyID` IS NOT NULL AND p1.`ID` IS NULL)
    OR (c.`DeliveryAddressID` IS NOT NULL AND p2.`ID` IS NULL)
    OR (c.`DivisionID` IS NOT NULL AND p3.`ID` IS NULL)
    OR (c.`InvoiceAddressID` IS NOT NULL AND p4.`ID` IS NULL)
    OR (c.`QuotationDeliveryTimeID` IS NOT NULL AND p5.`ID` IS NULL)
    OR (c.`QuotationFeedbackStatusID` IS NOT NULL AND p6.`ID` IS NULL)
    OR (c.`QuotationPaymentTermID` IS NOT NULL AND p7.`ID` IS NULL)
    OR (c.`QuotationPriceDescID` IS NOT NULL AND p8.`ID` IS NULL)
    OR (c.`QuotationSalesTermID` IS NOT NULL AND p9.`ID` IS NULL)
    OR (c.`QuotationStatusID` IS NOT NULL AND p10.`ID` IS NULL)
    OR (c.`QuotationStockAvailibilityID` IS NOT NULL AND p11.`ID` IS NULL);
SET @orphans = @orphans + @n;
SELECT COUNT(*) INTO @n FROM `quotation` c
  LEFT JOIN `quotationsubject` p0 ON c.`QuotationSubjectID` = p0.`ID`
  LEFT JOIN `quotationtitle` p1 ON c.`QuotationTitleID` = p1.`ID`
  LEFT JOIN `quotationtype` p2 ON c.`QuotationTypeID` = p2.`ID`
  LEFT JOIN `quotationvalidity` p3 ON c.`QuotationValidityID` = p3.`ID`
  LEFT JOIN `quotation` p4 ON c.`ReferenceQuotationID` = p4.`ID`
  LEFT JOIN `users` p5 ON c.`UserIDInput` = p5.`ID`
  LEFT JOIN `users` p6 ON c.`UserIDSales` = p6.`ID`
 WHERE  (c.`QuotationSubjectID` IS NOT NULL AND p0.`ID` IS NULL)
    OR (c.`QuotationTitleID` IS NOT NULL AND p1.`ID` IS NULL)
    OR (c.`QuotationTypeID` IS NOT NULL AND p2.`ID` IS NULL)
    OR (c.`QuotationValidityID` IS NOT NULL AND p3.`ID` IS NULL)
    OR (c.`ReferenceQuotationID` IS NOT NULL AND p4.`ID` IS NULL)
    OR (c.`UserIDInput` IS NOT NULL AND p5.`ID` IS NULL)
    OR (c.`UserIDSales` IS NOT NULL AND p6.`ID` IS NULL);
SET @orphans = @orphans + @n;

-- Two-row subquery on the false branch: ERROR 1242, an abort, before 40_fk runs.
SELECT IF(@orphans = 0,
          'quotation: orphan check OK - 0 rows across 19 relationships',
          (SELECT CONCAT('ABORT: ', @orphans, ' rows in `quotation` still reference a parent that does not exist')
           UNION ALL
           SELECT 'Run 20_cleanup for this table first. Do NOT skip this check.')
       ) AS preflight_orphans;

INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), 'a84a498d4c8e9519cee512eac9546f278a808e4550b5c207f5815faaa8e2f53d', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;
-- ---------------------------------------------------------------------
-- quotation/40_fk  --  source section 8
-- The constraints, one ALTER for the whole table.
--
-- FOREIGN_KEY_CHECKS is turned OFF for this statement and restored afterwards. With
-- checks on InnoDB cannot add a foreign key in place -- it copies the whole table and
-- re-validates every row, which is where the original migration's 154 table copies
-- came from. The validation is not skipped, it MOVED: 30_assert proves zero orphans
-- and refuses to let this run otherwise.
-- ---------------------------------------------------------------------

SET @fk_prev = @@FOREIGN_KEY_CHECKS;

SET @step = 'quotation/40_fk';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'ALTER TABLE `quotation`
  ADD CONSTRAINT `quotation_CompanyCPID` FOREIGN KEY (`CompanyCPID`) REFERENCES `companycp` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_CompanyID` FOREIGN KEY (`CompanyID`) REFERENCES `company` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_DeliveryAddressID` FOREIGN KEY (`DeliveryAddressID`) REFERENCES `companycp` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_DivisionID` FOREIGN KEY (`DivisionID`) REFERENCES `division` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_InvoiceAddressID` FOREIGN KEY (`InvoiceAddressID`) REFERENCES `companycp` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_QuotationDeliveryTimeID` FOREIGN KEY (`QuotationDeliveryTimeID`) REFERENCES `quotationdeliverytime` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_QuotationFeedbackStatusID` FOREIGN KEY (`QuotationFeedbackStatusID`) REFERENCES `quotationfeedbackstatus` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_QuotationPaymentTermID` FOREIGN KEY (`QuotationPaymentTermID`) REFERENCES `quotationpaymentterm` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_QuotationPriceDescID` FOREIGN KEY (`QuotationPriceDescID`) REFERENCES `quotationpricedesc` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_QuotationSalesTermID` FOREIGN KEY (`QuotationSalesTermID`) REFERENCES `quotationsalesterm` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_QuotationStatusID` FOREIGN KEY (`QuotationStatusID`) REFERENCES `quotationstatus` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_QuotationStockAvailibilityID` FOREIGN KEY (`QuotationStockAvailibilityID`) REFERENCES `quotationstockavailability` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_QuotationSubjectID` FOREIGN KEY (`QuotationSubjectID`) REFERENCES `quotationsubject` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_QuotationTitleID` FOREIGN KEY (`QuotationTitleID`) REFERENCES `quotationtitle` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_QuotationTypeID` FOREIGN KEY (`QuotationTypeID`) REFERENCES `quotationtype` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_QuotationValidityID` FOREIGN KEY (`QuotationValidityID`) REFERENCES `quotationvalidity` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_ReferenceQuotationID` FOREIGN KEY (`ReferenceQuotationID`) REFERENCES `quotation` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_UserIDInput` FOREIGN KEY (`UserIDInput`) REFERENCES `users` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotation_UserIDSales` FOREIGN KEY (`UserIDSales`) REFERENCES `users` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '46e255bee5dcd01bc131792a31c0d57756ef26c3a90ec85e8ccc00161f154e7f', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;


-- ---------------------------------------------------------------------
-- unit: quotationassignment    (detail, owned by quotation)
-- ---------------------------------------------------------------------
             AND @@SQL_MODE LIKE '%NO_AUTO_VALUE_ON_ZERO%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_DATE%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_IN_DATE%',
        @@SQL_MODE,
        CONCAT(REPLACE(REPLACE(@@SQL_MODE, 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', ''),
               ',NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES'));
-- ---------------------------------------------------------------------
-- quotationassignment/10_alter  --  source section A
-- The merged ALTER: new columns, type/charset changes, column order, NULL relaxation
-- and the indexes every foreign key needs -- ONE statement, so InnoDB rebuilds this
-- table ONCE. The original migration split these across five sections and rebuilt some
-- tables four times; that is where its 12 hours went. Do not split this back up.
--
-- Clause order is the original section order (3b, 3d, 3e, 4, 7), which is what keeps
-- every ADD COLUMN ... AFTER positioned exactly as before.
-- ---------------------------------------------------------------------

SET @step = 'quotationassignment/10_alter';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'ALTER TABLE `quotationassignment`
  MODIFY COLUMN `QuotationID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationStatusID` int DEFAULT NULL,
  MODIFY COLUMN `UserID` int DEFAULT NULL,
  ADD INDEX `qta_fk_QuotationID` (`QuotationID`),
  ADD INDEX `qta_fk_QuotationStatusID` (`QuotationStatusID`),
  ADD INDEX `qta_fk_UserID` (`UserID`)', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '794978597789479ca3c37cdb6546ec3e5908258da4684c06d695a9e6494570b7', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;
-- ---------------------------------------------------------------------
-- quotationassignment/20_cleanup  --  source section B (= the original sections 5 and 6, merged)
-- 0-sentinels AND orphaned references -> NULL, one pass per table.
--     section 5:  WHERE `c` = 0
--     section 6:  WHERE `c` IS NOT NULL AND parent.ID IS NULL
--     merged   :  WHERE `c` IS NOT NULL AND (`c` = 0 OR parent.ID IS NULL)
-- Every LEFT JOIN is a primary-key equality, so none of them can multiply rows.
--
-- DATA CHANGE, and this script cannot reverse it. Run the audit first.
-- In its own transaction, with the ledger row inside it: either the cleanup and the
-- record of it both land, or neither does.
-- ---------------------------------------------------------------------

START TRANSACTION;

SET @step = 'quotationassignment/20_cleanup';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'UPDATE `quotationassignment` c
  LEFT JOIN `quotation` p0 ON c.`QuotationID` = p0.`ID`
  LEFT JOIN `quotationstatus` p1 ON c.`QuotationStatusID` = p1.`ID`
  LEFT JOIN `users` p2 ON c.`UserID` = p2.`ID`
   SET
       c.`QuotationID` = IF((c.`QuotationID` IS NOT NULL AND (c.`QuotationID` = 0 OR p0.`ID` IS NULL)), NULL, c.`QuotationID`),
       c.`QuotationStatusID` = IF((c.`QuotationStatusID` IS NOT NULL AND (c.`QuotationStatusID` = 0 OR p1.`ID` IS NULL)), NULL, c.`QuotationStatusID`),
       c.`UserID` = IF((c.`UserID` IS NOT NULL AND (c.`UserID` = 0 OR p2.`ID` IS NULL)), NULL, c.`UserID`)
 WHERE  (c.`QuotationID` IS NOT NULL AND (c.`QuotationID` = 0 OR p0.`ID` IS NULL))
    OR (c.`QuotationStatusID` IS NOT NULL AND (c.`QuotationStatusID` = 0 OR p1.`ID` IS NULL))
    OR (c.`UserID` IS NOT NULL AND (c.`UserID` = 0 OR p2.`ID` IS NULL))', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '7faa193b15de79179380a0f819451789d2da9b1302f1af021cc9ac9f0ce5acac', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;

COMMIT;
-- ---------------------------------------------------------------------
-- quotationassignment/30_assert  --  source section 7b
-- Orphan assertion over this table's 3 foreign-key relationships.
--
-- This is what pays for 40_fk being fast. The original created the constraints with
-- FOREIGN_KEY_CHECKS ON so a row the cleanup missed would abort the run, and paid for it
-- with a full table copy per constraint. This buys the same protection with one indexed
-- pass, and 40_fk then adds the constraints as metadata only.
--
-- NOT ledger-guarded on the way in, unlike every other phase: it only reads, and the
-- point of it is to be re-runnable. It does record its ledger row -- 40_fk requires it.
--
-- IF THIS ABORTS: a row survived 20_cleanup. Same stop-and-look event the original would
-- have hit as ERROR 1452 inside section 8, but nothing has been created yet.
-- ---------------------------------------------------------------------

SET @step = 'quotationassignment/30_assert';
SET @t0 = NOW(3);
SET @orphans = 0;
SELECT COUNT(*) INTO @n FROM `quotationassignment` c
  LEFT JOIN `quotation` p0 ON c.`QuotationID` = p0.`ID`
  LEFT JOIN `quotationstatus` p1 ON c.`QuotationStatusID` = p1.`ID`
  LEFT JOIN `users` p2 ON c.`UserID` = p2.`ID`
 WHERE  (c.`QuotationID` IS NOT NULL AND p0.`ID` IS NULL)
    OR (c.`QuotationStatusID` IS NOT NULL AND p1.`ID` IS NULL)
    OR (c.`UserID` IS NOT NULL AND p2.`ID` IS NULL);
SET @orphans = @orphans + @n;

-- Two-row subquery on the false branch: ERROR 1242, an abort, before 40_fk runs.
SELECT IF(@orphans = 0,
          'quotationassignment: orphan check OK - 0 rows across 3 relationships',
          (SELECT CONCAT('ABORT: ', @orphans, ' rows in `quotationassignment` still reference a parent that does not exist')
           UNION ALL
           SELECT 'Run 20_cleanup for this table first. Do NOT skip this check.')
       ) AS preflight_orphans;

INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), 'b9045cdc08b099a5bae7132c0f1e0d6c91c22eda20903ed2391767a0e72fbb1b', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;
-- ---------------------------------------------------------------------
-- quotationassignment/40_fk  --  source section 8
-- The constraints, one ALTER for the whole table.
--
-- FOREIGN_KEY_CHECKS is turned OFF for this statement and restored afterwards. With
-- checks on InnoDB cannot add a foreign key in place -- it copies the whole table and
-- re-validates every row, which is where the original migration's 154 table copies
-- came from. The validation is not skipped, it MOVED: 30_assert proves zero orphans
-- and refuses to let this run otherwise.
-- ---------------------------------------------------------------------

SET @fk_prev = @@FOREIGN_KEY_CHECKS;

SET @step = 'quotationassignment/40_fk';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'ALTER TABLE `quotationassignment`
  ADD CONSTRAINT `qta_fk_QuotationID` FOREIGN KEY (`QuotationID`) REFERENCES `quotation` (`ID`) ON DELETE SET NULL ON UPDATE CASCADE,
  ADD CONSTRAINT `qta_fk_QuotationStatusID` FOREIGN KEY (`QuotationStatusID`) REFERENCES `quotationstatus` (`ID`) ON DELETE SET NULL ON UPDATE CASCADE,
  ADD CONSTRAINT `qta_fk_UserID` FOREIGN KEY (`UserID`) REFERENCES `users` (`ID`) ON DELETE SET NULL ON UPDATE CASCADE,
  ADD CONSTRAINT `quotationassignment_QuotationID` FOREIGN KEY (`QuotationID`) REFERENCES `quotation` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationassignment_QuotationStatusID` FOREIGN KEY (`QuotationStatusID`) REFERENCES `quotationstatus` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationassignment_UserID` FOREIGN KEY (`UserID`) REFERENCES `users` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '3d2f3aac6d94bc6db8d62b7a7476b7e1273d21cf196da9fbd1cd253eca61e6fc', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;


-- ---------------------------------------------------------------------
-- unit: quotationdetails    (detail, owned by quotation)
-- ---------------------------------------------------------------------
             AND @@SQL_MODE LIKE '%NO_AUTO_VALUE_ON_ZERO%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_DATE%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_IN_DATE%',
        @@SQL_MODE,
        CONCAT(REPLACE(REPLACE(@@SQL_MODE, 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', ''),
               ',NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES'));
-- ---------------------------------------------------------------------
-- quotationdetails/10_alter  --  source section A
-- The merged ALTER: new columns, type/charset changes, column order, NULL relaxation
-- and the indexes every foreign key needs -- ONE statement, so InnoDB rebuilds this
-- table ONCE. The original migration split these across five sections and rebuilt some
-- tables four times; that is where its 12 hours went. Do not split this back up.
--
-- Clause order is the original section order (3b, 3d, 3e, 4, 7), which is what keeps
-- every ADD COLUMN ... AFTER positioned exactly as before.
-- ---------------------------------------------------------------------

SET @step = 'quotationdetails/10_alter';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'ALTER TABLE `quotationdetails`
  ADD COLUMN `ItemOriginID` int DEFAULT NULL AFTER `ProjectIDDetails`,
  MODIFY COLUMN `QuotationID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationDetailStatusID` int DEFAULT NULL,
  MODIFY COLUMN `ProjectID` int DEFAULT NULL,
  MODIFY COLUMN `ProjectIDDetails` int DEFAULT NULL,
  MODIFY COLUMN `BarangID` int DEFAULT NULL,
  MODIFY COLUMN `ApplicationID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationPackID` int DEFAULT NULL,
  MODIFY COLUMN `PackingID` int DEFAULT NULL,
  MODIFY COLUMN `SatuanID` int DEFAULT NULL,
  MODIFY COLUMN `SatuanOrderQty` int DEFAULT NULL,
  ADD INDEX `quotationdetails_ApplicationID` (`ApplicationID`),
  ADD INDEX `quotationdetails_BarangID` (`BarangID`),
  ADD INDEX `quotationdetails_ItemOriginID` (`ItemOriginID`),
  ADD INDEX `quotationdetails_PackingID` (`PackingID`),
  ADD INDEX `quotationdetails_ProjectID` (`ProjectID`),
  ADD INDEX `quotationdetails_ProjectIDDetails` (`ProjectIDDetails`),
  ADD INDEX `quotationdetails_QuotationDetailStatusID` (`QuotationDetailStatusID`),
  ADD INDEX `quotationdetails_QuotationID` (`QuotationID`),
  ADD INDEX `quotationdetails_QuotationPackID` (`QuotationPackID`),
  ADD INDEX `quotationdetails_SatuanID` (`SatuanID`),
  ADD INDEX `quotationdetails_SatuanOrderQty` (`SatuanOrderQty`)', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '5d544291a14c791f2f55a60152a99a46e8d8b1b6881c34a659636bcb7b98b43a', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;
-- ---------------------------------------------------------------------
-- quotationdetails/15_repair  --  source section 4c
-- One-off data repair, guarded three ways so it can only touch the rows it describes.
-- Runs after 10_alter and before 20_cleanup, exactly as in the source file: 20_cleanup
-- turns the 0-sentinel this leaves behind into NULL.
-- ---------------------------------------------------------------------

SET @step = 'quotationdetails/15_repair';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'UPDATE `quotationdetails` d
  JOIN `packing` p ON p.`ID` = d.`QuotationPackID`
  LEFT JOIN `quotationpack` qp ON qp.`ID` = d.`QuotationPackID`
SET d.`PackingID` = d.`QuotationPackID`,
    d.`QuotationPackID` = 0
WHERE d.`QuotationPackID` IS NOT NULL
  AND d.`QuotationPackID` <> 0
  AND qp.`ID` IS NULL
  AND (d.`PackingID` = 0 OR d.`PackingID` IS NULL)', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '3c40f473fb3747f5b32954dd121cbc3525750ab44654637be7c9b1550be142ac', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;
-- ---------------------------------------------------------------------
-- quotationdetails/20_cleanup  --  source section B (= the original sections 5 and 6, merged)
-- 0-sentinels AND orphaned references -> NULL, one pass per table.
--     section 5:  WHERE `c` = 0
--     section 6:  WHERE `c` IS NOT NULL AND parent.ID IS NULL
--     merged   :  WHERE `c` IS NOT NULL AND (`c` = 0 OR parent.ID IS NULL)
-- Every LEFT JOIN is a primary-key equality, so none of them can multiply rows.
--
-- DATA CHANGE, and this script cannot reverse it. Run the audit first.
-- In its own transaction, with the ledger row inside it: either the cleanup and the
-- record of it both land, or neither does.
-- ---------------------------------------------------------------------

START TRANSACTION;

SET @step = 'quotationdetails/20_cleanup';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'UPDATE `quotationdetails` c
  LEFT JOIN `application` p0 ON c.`ApplicationID` = p0.`ID`
  LEFT JOIN `barang` p1 ON c.`BarangID` = p1.`ID`
  LEFT JOIN `packing` p2 ON c.`PackingID` = p2.`ID`
  LEFT JOIN `companyprojectdetailcc` p3 ON c.`ProjectID` = p3.`ID`
  LEFT JOIN `quotationdetailstatus` p4 ON c.`QuotationDetailStatusID` = p4.`ID`
  LEFT JOIN `quotation` p5 ON c.`QuotationID` = p5.`ID`
  LEFT JOIN `quotationpack` p6 ON c.`QuotationPackID` = p6.`ID`
  LEFT JOIN `satuan` p7 ON c.`SatuanID` = p7.`ID`
  LEFT JOIN `satuan` p8 ON c.`SatuanOrderQty` = p8.`ID`
   SET
       c.`ApplicationID` = IF((c.`ApplicationID` IS NOT NULL AND (c.`ApplicationID` = 0 OR p0.`ID` IS NULL)), NULL, c.`ApplicationID`),
       c.`BarangID` = IF((c.`BarangID` IS NOT NULL AND (c.`BarangID` = 0 OR p1.`ID` IS NULL)), NULL, c.`BarangID`),
       c.`PackingID` = IF((c.`PackingID` IS NOT NULL AND (c.`PackingID` = 0 OR p2.`ID` IS NULL)), NULL, c.`PackingID`),
       c.`ProjectID` = IF((c.`ProjectID` IS NOT NULL AND (c.`ProjectID` = 0 OR p3.`ID` IS NULL)), NULL, c.`ProjectID`),
       c.`QuotationDetailStatusID` = IF((c.`QuotationDetailStatusID` IS NOT NULL AND (c.`QuotationDetailStatusID` = 0 OR p4.`ID` IS NULL)), NULL, c.`QuotationDetailStatusID`),
       c.`QuotationID` = IF((c.`QuotationID` IS NOT NULL AND (c.`QuotationID` = 0 OR p5.`ID` IS NULL)), NULL, c.`QuotationID`),
       c.`QuotationPackID` = IF((c.`QuotationPackID` IS NOT NULL AND (c.`QuotationPackID` = 0 OR p6.`ID` IS NULL)), NULL, c.`QuotationPackID`),
       c.`SatuanID` = IF((c.`SatuanID` IS NOT NULL AND (c.`SatuanID` = 0 OR p7.`ID` IS NULL)), NULL, c.`SatuanID`),
       c.`SatuanOrderQty` = IF((c.`SatuanOrderQty` IS NOT NULL AND (c.`SatuanOrderQty` = 0 OR p8.`ID` IS NULL)), NULL, c.`SatuanOrderQty`)
 WHERE  (c.`ApplicationID` IS NOT NULL AND (c.`ApplicationID` = 0 OR p0.`ID` IS NULL))
    OR (c.`BarangID` IS NOT NULL AND (c.`BarangID` = 0 OR p1.`ID` IS NULL))
    OR (c.`PackingID` IS NOT NULL AND (c.`PackingID` = 0 OR p2.`ID` IS NULL))
    OR (c.`ProjectID` IS NOT NULL AND (c.`ProjectID` = 0 OR p3.`ID` IS NULL))
    OR (c.`QuotationDetailStatusID` IS NOT NULL AND (c.`QuotationDetailStatusID` = 0 OR p4.`ID` IS NULL))
    OR (c.`QuotationID` IS NOT NULL AND (c.`QuotationID` = 0 OR p5.`ID` IS NULL))
    OR (c.`QuotationPackID` IS NOT NULL AND (c.`QuotationPackID` = 0 OR p6.`ID` IS NULL))
    OR (c.`SatuanID` IS NOT NULL AND (c.`SatuanID` = 0 OR p7.`ID` IS NULL))
    OR (c.`SatuanOrderQty` IS NOT NULL AND (c.`SatuanOrderQty` = 0 OR p8.`ID` IS NULL))', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '4e8e6d15a4d8e6f4699be9b207de5eaeb05dc9963b03f005c445448dc08daa3d', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;

COMMIT;
-- ---------------------------------------------------------------------
-- quotationdetails/30_assert  --  source section 7b
-- Orphan assertion over this table's 10 foreign-key relationships.
--
-- This is what pays for 40_fk being fast. The original created the constraints with
-- FOREIGN_KEY_CHECKS ON so a row the cleanup missed would abort the run, and paid for it
-- with a full table copy per constraint. This buys the same protection with one indexed
-- pass, and 40_fk then adds the constraints as metadata only.
--
-- NOT ledger-guarded on the way in, unlike every other phase: it only reads, and the
-- point of it is to be re-runnable. It does record its ledger row -- 40_fk requires it.
--
-- IF THIS ABORTS: a row survived 20_cleanup. Same stop-and-look event the original would
-- have hit as ERROR 1452 inside section 8, but nothing has been created yet.
-- ---------------------------------------------------------------------

SET @step = 'quotationdetails/30_assert';
SET @t0 = NOW(3);
SET @orphans = 0;
SELECT COUNT(*) INTO @n FROM `quotationdetails` c
  LEFT JOIN `application` p0 ON c.`ApplicationID` = p0.`ID`
  LEFT JOIN `barang` p1 ON c.`BarangID` = p1.`ID`
  LEFT JOIN `quotationitemorigin` p2 ON c.`ItemOriginID` = p2.`ID`
  LEFT JOIN `packing` p3 ON c.`PackingID` = p3.`ID`
  LEFT JOIN `companyprojectdetailcc` p4 ON c.`ProjectID` = p4.`ID`
  LEFT JOIN `quotationdetailstatus` p5 ON c.`QuotationDetailStatusID` = p5.`ID`
  LEFT JOIN `quotation` p6 ON c.`QuotationID` = p6.`ID`
  LEFT JOIN `quotationpack` p7 ON c.`QuotationPackID` = p7.`ID`
  LEFT JOIN `satuan` p8 ON c.`SatuanID` = p8.`ID`
  LEFT JOIN `satuan` p9 ON c.`SatuanOrderQty` = p9.`ID`
 WHERE  (c.`ApplicationID` IS NOT NULL AND p0.`ID` IS NULL)
    OR (c.`BarangID` IS NOT NULL AND p1.`ID` IS NULL)
    OR (c.`ItemOriginID` IS NOT NULL AND p2.`ID` IS NULL)
    OR (c.`PackingID` IS NOT NULL AND p3.`ID` IS NULL)
    OR (c.`ProjectID` IS NOT NULL AND p4.`ID` IS NULL)
    OR (c.`QuotationDetailStatusID` IS NOT NULL AND p5.`ID` IS NULL)
    OR (c.`QuotationID` IS NOT NULL AND p6.`ID` IS NULL)
    OR (c.`QuotationPackID` IS NOT NULL AND p7.`ID` IS NULL)
    OR (c.`SatuanID` IS NOT NULL AND p8.`ID` IS NULL)
    OR (c.`SatuanOrderQty` IS NOT NULL AND p9.`ID` IS NULL);
SET @orphans = @orphans + @n;

-- Two-row subquery on the false branch: ERROR 1242, an abort, before 40_fk runs.
SELECT IF(@orphans = 0,
          'quotationdetails: orphan check OK - 0 rows across 10 relationships',
          (SELECT CONCAT('ABORT: ', @orphans, ' rows in `quotationdetails` still reference a parent that does not exist')
           UNION ALL
           SELECT 'Run 20_cleanup for this table first. Do NOT skip this check.')
       ) AS preflight_orphans;

INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '87d323c3ac0e565c710296ffb5f94f00e81ba9985c8db9c74a4e33a6b3054f44', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;
-- ---------------------------------------------------------------------
-- quotationdetails/40_fk  --  source section 8
-- The constraints, one ALTER for the whole table.
--
-- FOREIGN_KEY_CHECKS is turned OFF for this statement and restored afterwards. With
-- checks on InnoDB cannot add a foreign key in place -- it copies the whole table and
-- re-validates every row, which is where the original migration's 154 table copies
-- came from. The validation is not skipped, it MOVED: 30_assert proves zero orphans
-- and refuses to let this run otherwise.
-- ---------------------------------------------------------------------

SET @fk_prev = @@FOREIGN_KEY_CHECKS;

SET @step = 'quotationdetails/40_fk';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'ALTER TABLE `quotationdetails`
  ADD CONSTRAINT `quotationdetails_ApplicationID` FOREIGN KEY (`ApplicationID`) REFERENCES `application` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetails_BarangID` FOREIGN KEY (`BarangID`) REFERENCES `barang` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetails_ItemOriginID` FOREIGN KEY (`ItemOriginID`) REFERENCES `quotationitemorigin` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetails_PackingID` FOREIGN KEY (`PackingID`) REFERENCES `packing` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetails_ProjectID` FOREIGN KEY (`ProjectID`) REFERENCES `companyprojectdetailcc` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetails_QuotationDetailStatusID` FOREIGN KEY (`QuotationDetailStatusID`) REFERENCES `quotationdetailstatus` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetails_QuotationID` FOREIGN KEY (`QuotationID`) REFERENCES `quotation` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetails_QuotationPackID` FOREIGN KEY (`QuotationPackID`) REFERENCES `quotationpack` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetails_SatuanID` FOREIGN KEY (`SatuanID`) REFERENCES `satuan` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetails_SatuanOrderQty` FOREIGN KEY (`SatuanOrderQty`) REFERENCES `satuan` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '80380d27173359a4e4f683a50b63529bc9e25b18f6efea7e6fb5e56e9ce695bb', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;


-- ---------------------------------------------------------------------
-- unit: quotationdetailsassignment    (detail, owned by quotation)
-- ---------------------------------------------------------------------
             AND @@SQL_MODE LIKE '%NO_AUTO_VALUE_ON_ZERO%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_DATE%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_IN_DATE%',
        @@SQL_MODE,
        CONCAT(REPLACE(REPLACE(@@SQL_MODE, 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', ''),
               ',NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES'));
-- ---------------------------------------------------------------------
-- quotationdetailsassignment/10_alter  --  source section A
-- The merged ALTER: new columns, type/charset changes, column order, NULL relaxation
-- and the indexes every foreign key needs -- ONE statement, so InnoDB rebuilds this
-- table ONCE. The original migration split these across five sections and rebuilt some
-- tables four times; that is where its 12 hours went. Do not split this back up.
--
-- Clause order is the original section order (3b, 3d, 3e, 4, 7), which is what keeps
-- every ADD COLUMN ... AFTER positioned exactly as before.
-- ---------------------------------------------------------------------

SET @step = 'quotationdetailsassignment/10_alter';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'ALTER TABLE `quotationdetailsassignment`
  MODIFY COLUMN `QuotationID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationDetailID` int DEFAULT NULL,
  MODIFY COLUMN `QuotationDetailStatusID` int DEFAULT NULL,
  MODIFY COLUMN `UserID` int DEFAULT NULL,
  ADD INDEX `qda_fk_QuotationDetailID` (`QuotationDetailID`),
  ADD INDEX `qda_fk_QuotationDetailStatusID` (`QuotationDetailStatusID`),
  ADD INDEX `qda_fk_QuotationID` (`QuotationID`),
  ADD INDEX `qda_fk_UserID` (`UserID`)', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), 'd5c2d8bf68e22967272acc6bcd6b989c983a975040f1b02cf88439c470204dff', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;
-- ---------------------------------------------------------------------
-- quotationdetailsassignment/20_cleanup  --  source section B (= the original sections 5 and 6, merged)
-- 0-sentinels AND orphaned references -> NULL, one pass per table.
--     section 5:  WHERE `c` = 0
--     section 6:  WHERE `c` IS NOT NULL AND parent.ID IS NULL
--     merged   :  WHERE `c` IS NOT NULL AND (`c` = 0 OR parent.ID IS NULL)
-- Every LEFT JOIN is a primary-key equality, so none of them can multiply rows.
--
-- DATA CHANGE, and this script cannot reverse it. Run the audit first.
-- In its own transaction, with the ledger row inside it: either the cleanup and the
-- record of it both land, or neither does.
-- ---------------------------------------------------------------------

START TRANSACTION;

SET @step = 'quotationdetailsassignment/20_cleanup';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'UPDATE `quotationdetailsassignment` c
  LEFT JOIN `quotationdetails` p0 ON c.`QuotationDetailID` = p0.`ID`
  LEFT JOIN `quotationdetailstatus` p1 ON c.`QuotationDetailStatusID` = p1.`ID`
  LEFT JOIN `quotation` p2 ON c.`QuotationID` = p2.`ID`
  LEFT JOIN `users` p3 ON c.`UserID` = p3.`ID`
   SET
       c.`QuotationDetailID` = IF((c.`QuotationDetailID` IS NOT NULL AND (c.`QuotationDetailID` = 0 OR p0.`ID` IS NULL)), NULL, c.`QuotationDetailID`),
       c.`QuotationDetailStatusID` = IF((c.`QuotationDetailStatusID` IS NOT NULL AND (c.`QuotationDetailStatusID` = 0 OR p1.`ID` IS NULL)), NULL, c.`QuotationDetailStatusID`),
       c.`QuotationID` = IF((c.`QuotationID` IS NOT NULL AND (c.`QuotationID` = 0 OR p2.`ID` IS NULL)), NULL, c.`QuotationID`),
       c.`UserID` = IF((c.`UserID` IS NOT NULL AND (c.`UserID` = 0 OR p3.`ID` IS NULL)), NULL, c.`UserID`)
 WHERE  (c.`QuotationDetailID` IS NOT NULL AND (c.`QuotationDetailID` = 0 OR p0.`ID` IS NULL))
    OR (c.`QuotationDetailStatusID` IS NOT NULL AND (c.`QuotationDetailStatusID` = 0 OR p1.`ID` IS NULL))
    OR (c.`QuotationID` IS NOT NULL AND (c.`QuotationID` = 0 OR p2.`ID` IS NULL))
    OR (c.`UserID` IS NOT NULL AND (c.`UserID` = 0 OR p3.`ID` IS NULL))', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), 'f8f88cf4b5429acd1b9801a3c054b1f615b5550999bff46efe848bf0bb00058f', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;

COMMIT;
-- ---------------------------------------------------------------------
-- quotationdetailsassignment/30_assert  --  source section 7b
-- Orphan assertion over this table's 4 foreign-key relationships.
--
-- This is what pays for 40_fk being fast. The original created the constraints with
-- FOREIGN_KEY_CHECKS ON so a row the cleanup missed would abort the run, and paid for it
-- with a full table copy per constraint. This buys the same protection with one indexed
-- pass, and 40_fk then adds the constraints as metadata only.
--
-- NOT ledger-guarded on the way in, unlike every other phase: it only reads, and the
-- point of it is to be re-runnable. It does record its ledger row -- 40_fk requires it.
--
-- IF THIS ABORTS: a row survived 20_cleanup. Same stop-and-look event the original would
-- have hit as ERROR 1452 inside section 8, but nothing has been created yet.
-- ---------------------------------------------------------------------

SET @step = 'quotationdetailsassignment/30_assert';
SET @t0 = NOW(3);
SET @orphans = 0;
SELECT COUNT(*) INTO @n FROM `quotationdetailsassignment` c
  LEFT JOIN `quotationdetails` p0 ON c.`QuotationDetailID` = p0.`ID`
  LEFT JOIN `quotationdetailstatus` p1 ON c.`QuotationDetailStatusID` = p1.`ID`
  LEFT JOIN `quotation` p2 ON c.`QuotationID` = p2.`ID`
  LEFT JOIN `users` p3 ON c.`UserID` = p3.`ID`
 WHERE  (c.`QuotationDetailID` IS NOT NULL AND p0.`ID` IS NULL)
    OR (c.`QuotationDetailStatusID` IS NOT NULL AND p1.`ID` IS NULL)
    OR (c.`QuotationID` IS NOT NULL AND p2.`ID` IS NULL)
    OR (c.`UserID` IS NOT NULL AND p3.`ID` IS NULL);
SET @orphans = @orphans + @n;

-- Two-row subquery on the false branch: ERROR 1242, an abort, before 40_fk runs.
SELECT IF(@orphans = 0,
          'quotationdetailsassignment: orphan check OK - 0 rows across 4 relationships',
          (SELECT CONCAT('ABORT: ', @orphans, ' rows in `quotationdetailsassignment` still reference a parent that does not exist')
           UNION ALL
           SELECT 'Run 20_cleanup for this table first. Do NOT skip this check.')
       ) AS preflight_orphans;

INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '7c0ea646dd5641dccc139fec726ecf30cf863a56d37e4432bd3fe517b414391f', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;
-- ---------------------------------------------------------------------
-- quotationdetailsassignment/40_fk  --  source section 8
-- The constraints, one ALTER for the whole table.
--
-- FOREIGN_KEY_CHECKS is turned OFF for this statement and restored afterwards. With
-- checks on InnoDB cannot add a foreign key in place -- it copies the whole table and
-- re-validates every row, which is where the original migration's 154 table copies
-- came from. The validation is not skipped, it MOVED: 30_assert proves zero orphans
-- and refuses to let this run otherwise.
-- ---------------------------------------------------------------------

SET @fk_prev = @@FOREIGN_KEY_CHECKS;

SET @step = 'quotationdetailsassignment/40_fk';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'ALTER TABLE `quotationdetailsassignment`
  ADD CONSTRAINT `qda_fk_QuotationDetailID` FOREIGN KEY (`QuotationDetailID`) REFERENCES `quotationdetails` (`ID`) ON DELETE SET NULL ON UPDATE CASCADE,
  ADD CONSTRAINT `qda_fk_QuotationDetailStatusID` FOREIGN KEY (`QuotationDetailStatusID`) REFERENCES `quotationdetailstatus` (`ID`) ON DELETE SET NULL ON UPDATE CASCADE,
  ADD CONSTRAINT `qda_fk_QuotationID` FOREIGN KEY (`QuotationID`) REFERENCES `quotation` (`ID`) ON DELETE SET NULL ON UPDATE CASCADE,
  ADD CONSTRAINT `qda_fk_UserID` FOREIGN KEY (`UserID`) REFERENCES `users` (`ID`) ON DELETE SET NULL ON UPDATE CASCADE,
  ADD CONSTRAINT `quotationdetailsassignment_QuotationDetailID` FOREIGN KEY (`QuotationDetailID`) REFERENCES `quotationdetails` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetailsassignment_QuotationDetailStatusID` FOREIGN KEY (`QuotationDetailStatusID`) REFERENCES `quotationdetailstatus` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetailsassignment_QuotationID` FOREIGN KEY (`QuotationID`) REFERENCES `quotation` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  ADD CONSTRAINT `quotationdetailsassignment_UserID` FOREIGN KEY (`UserID`) REFERENCES `users` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '075a8c5198e43c14b4e42faf9b7814b1e09fb81672baba8d4d02484004b4e8fb', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;


-- ---------------------------------------------------------------------
-- unit: quotationlink    (detail, owned by quotation)
-- ---------------------------------------------------------------------
             AND @@SQL_MODE LIKE '%NO_AUTO_VALUE_ON_ZERO%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_DATE%'
             AND @@SQL_MODE NOT LIKE '%NO_ZERO_IN_DATE%',
        @@SQL_MODE,
        CONCAT(REPLACE(REPLACE(@@SQL_MODE, 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', ''),
               ',NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES'));
-- ---------------------------------------------------------------------
-- quotationlink/10_alter  --  source section A
-- The merged ALTER: new columns, type/charset changes, column order, NULL relaxation
-- and the indexes every foreign key needs -- ONE statement, so InnoDB rebuilds this
-- table ONCE. The original migration split these across five sections and rebuilt some
-- tables four times; that is where its 12 hours went. Do not split this back up.
--
-- Clause order is the original section order (3b, 3d, 3e, 4, 7), which is what keeps
-- every ADD COLUMN ... AFTER positioned exactly as before.
-- ---------------------------------------------------------------------

SET @step = 'quotationlink/10_alter';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'ALTER TABLE `quotationlink`
  MODIFY COLUMN `QuotationID` int DEFAULT NULL,
  ADD INDEX `ql_fk_QuotationID` (`QuotationID`)', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '1cc3eb00acde568afc61650e423055c60ac6a26ce50716c38235b509c528b73b', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;
-- ---------------------------------------------------------------------
-- quotationlink/20_cleanup  --  source section B (= the original sections 5 and 6, merged)
-- 0-sentinels AND orphaned references -> NULL, one pass per table.
--     section 5:  WHERE `c` = 0
--     section 6:  WHERE `c` IS NOT NULL AND parent.ID IS NULL
--     merged   :  WHERE `c` IS NOT NULL AND (`c` = 0 OR parent.ID IS NULL)
-- Every LEFT JOIN is a primary-key equality, so none of them can multiply rows.
--
-- DATA CHANGE, and this script cannot reverse it. Run the audit first.
-- In its own transaction, with the ledger row inside it: either the cleanup and the
-- record of it both land, or neither does.
-- ---------------------------------------------------------------------

START TRANSACTION;

SET @step = 'quotationlink/20_cleanup';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'UPDATE `quotationlink` c
  LEFT JOIN `quotation` p0 ON c.`QuotationID` = p0.`ID`
   SET
       c.`QuotationID` = IF((c.`QuotationID` IS NOT NULL AND (c.`QuotationID` = 0 OR p0.`ID` IS NULL)), NULL, c.`QuotationID`)
 WHERE  (c.`QuotationID` IS NOT NULL AND (c.`QuotationID` = 0 OR p0.`ID` IS NULL))', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '9c6d6314fb346915eb68dc91f196059d3f9a9314db1d8b00db58decbd0cd388e', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;

COMMIT;
-- ---------------------------------------------------------------------
-- quotationlink/30_assert  --  source section 7b
-- Orphan assertion over this table's 1 foreign-key relationships.
--
-- This is what pays for 40_fk being fast. The original created the constraints with
-- FOREIGN_KEY_CHECKS ON so a row the cleanup missed would abort the run, and paid for it
-- with a full table copy per constraint. This buys the same protection with one indexed
-- pass, and 40_fk then adds the constraints as metadata only.
--
-- NOT ledger-guarded on the way in, unlike every other phase: it only reads, and the
-- point of it is to be re-runnable. It does record its ledger row -- 40_fk requires it.
--
-- IF THIS ABORTS: a row survived 20_cleanup. Same stop-and-look event the original would
-- have hit as ERROR 1452 inside section 8, but nothing has been created yet.
-- ---------------------------------------------------------------------

SET @step = 'quotationlink/30_assert';
SET @t0 = NOW(3);
SET @orphans = 0;
SELECT COUNT(*) INTO @n FROM `quotationlink` c
  LEFT JOIN `quotation` p0 ON c.`QuotationID` = p0.`ID`
 WHERE  (c.`QuotationID` IS NOT NULL AND p0.`ID` IS NULL);
SET @orphans = @orphans + @n;

-- Two-row subquery on the false branch: ERROR 1242, an abort, before 40_fk runs.
SELECT IF(@orphans = 0,
          'quotationlink: orphan check OK - 0 rows across 1 relationships',
          (SELECT CONCAT('ABORT: ', @orphans, ' rows in `quotationlink` still reference a parent that does not exist')
           UNION ALL
           SELECT 'Run 20_cleanup for this table first. Do NOT skip this check.')
       ) AS preflight_orphans;

INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), '4cca5cb0982a60c2cdd59f33665af0b2f07a791b3ce5347de641550a6070e53e', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;
-- ---------------------------------------------------------------------
-- quotationlink/40_fk  --  source section 8
-- The constraints, one ALTER for the whole table.
--
-- FOREIGN_KEY_CHECKS is turned OFF for this statement and restored afterwards. With
-- checks on InnoDB cannot add a foreign key in place -- it copies the whole table and
-- re-validates every row, which is where the original migration's 154 table copies
-- came from. The validation is not skipped, it MOVED: 30_assert proves zero orphans
-- and refuses to let this run otherwise.
-- ---------------------------------------------------------------------

SET @fk_prev = @@FOREIGN_KEY_CHECKS;

SET @step = 'quotationlink/40_fk';
SET @todo = (SELECT COUNT(*) = 0 FROM `_migration_ledger` WHERE `step` = @step);
SET @t0 = NOW(3);
SET @sql = IF(@todo, 'ALTER TABLE `quotationlink`
  ADD CONSTRAINT `ql_fk_QuotationID` FOREIGN KEY (`QuotationID`) REFERENCES `quotation` (`ID`) ON DELETE SET NULL ON UPDATE CASCADE,
  ADD CONSTRAINT `quotationlink_QuotationID` FOREIGN KEY (`QuotationID`) REFERENCES `quotation` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT', 'DO 0');
PREPARE _step FROM @sql; EXECUTE _step; DEALLOCATE PREPARE _step;
INSERT INTO `_migration_ledger` (`step`, `applied_at`, `checksum`, `duration_ms`)
     VALUES (@step, NOW(), 'c900e0d9381623bbc129b938a0ad3903de551a3b32ffab5f58f2e33f3f162141', TIMESTAMPDIFF(MICROSECOND, @t0, NOW(3)) DIV 1000)
ON DUPLICATE KEY UPDATE `step` = `step`;


-- --------------------------------------------------------------- postflight
-- DID THE WORK ACTUALLY LAND?
--
-- Every step records itself in `_migration_ledger` immediately after its statement runs,
-- WITHOUT checking that the statement succeeded. That is inherited from step/ unchanged,
-- and there it is safe because run.sh runs mysql without --force, so the first error stops
-- everything. Imported by hand, the client decides -- and "continue on error" would leave
-- failed steps marked applied.
--
-- So this file checks its own work against information_schema before it finishes. It is
-- the only thing here that is NOT copied from step/. It reads; it changes nothing.

-- postflight-set: fk
SET @want_fk = 45;
SELECT COUNT(*), IFNULL(SUBSTRING(GROUP_CONCAT(n ORDER BY n SEPARATOR ', '), 1, 60), '')
  INTO @missn_fk, @miss_fk
  FROM (SELECT 'quotation.quotation_CompanyCPID' AS n UNION ALL SELECT 'quotation.quotation_CompanyID' UNION ALL SELECT 'quotation.quotation_DeliveryAddressID' UNION ALL SELECT 'quotation.quotation_DivisionID' UNION ALL SELECT 'quotation.quotation_InvoiceAddressID' UNION ALL SELECT 'quotation.quotation_QuotationDeliveryTimeID' UNION ALL SELECT 'quotation.quotation_QuotationFeedbackStatusID' UNION ALL SELECT 'quotation.quotation_QuotationPaymentTermID' UNION ALL SELECT 'quotation.quotation_QuotationPriceDescID' UNION ALL SELECT 'quotation.quotation_QuotationSalesTermID' UNION ALL SELECT 'quotation.quotation_QuotationStatusID' UNION ALL SELECT 'quotation.quotation_QuotationStockAvailibilityID' UNION ALL SELECT 'quotation.quotation_QuotationSubjectID' UNION ALL SELECT 'quotation.quotation_QuotationTitleID' UNION ALL SELECT 'quotation.quotation_QuotationTypeID' UNION ALL SELECT 'quotation.quotation_QuotationValidityID' UNION ALL SELECT 'quotation.quotation_ReferenceQuotationID' UNION ALL SELECT 'quotation.quotation_UserIDInput' UNION ALL SELECT 'quotation.quotation_UserIDSales' UNION ALL SELECT 'quotationassignment.qta_fk_QuotationID' UNION ALL SELECT 'quotationassignment.qta_fk_QuotationStatusID' UNION ALL SELECT 'quotationassignment.qta_fk_UserID' UNION ALL SELECT 'quotationassignment.quotationassignment_QuotationID' UNION ALL SELECT 'quotationassignment.quotationassignment_QuotationStatusID' UNION ALL SELECT 'quotationassignment.quotationassignment_UserID' UNION ALL SELECT 'quotationdetails.quotationdetails_ApplicationID' UNION ALL SELECT 'quotationdetails.quotationdetails_BarangID' UNION ALL SELECT 'quotationdetails.quotationdetails_ItemOriginID' UNION ALL SELECT 'quotationdetails.quotationdetails_PackingID' UNION ALL SELECT 'quotationdetails.quotationdetails_ProjectID' UNION ALL SELECT 'quotationdetails.quotationdetails_QuotationDetailStatusID' UNION ALL SELECT 'quotationdetails.quotationdetails_QuotationID' UNION ALL SELECT 'quotationdetails.quotationdetails_QuotationPackID' UNION ALL SELECT 'quotationdetails.quotationdetails_SatuanID' UNION ALL SELECT 'quotationdetails.quotationdetails_SatuanOrderQty' UNION ALL SELECT 'quotationdetailsassignment.qda_fk_QuotationDetailID' UNION ALL SELECT 'quotationdetailsassignment.qda_fk_QuotationDetailStatusID' UNION ALL SELECT 'quotationdetailsassignment.qda_fk_QuotationID' UNION ALL SELECT 'quotationdetailsassignment.qda_fk_UserID' UNION ALL SELECT 'quotationdetailsassignment.quotationdetailsassignment_QuotationDetailID' UNION ALL SELECT 'quotationdetailsassignment.quotationdetailsassignment_QuotationDetailStatusID' UNION ALL SELECT 'quotationdetailsassignment.quotationdetailsassignment_QuotationID' UNION ALL SELECT 'quotationdetailsassignment.quotationdetailsassignment_UserID' UNION ALL SELECT 'quotationlink.ql_fk_QuotationID' UNION ALL SELECT 'quotationlink.quotationlink_QuotationID') _w
 WHERE n NOT IN (SELECT CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME) FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = DATABASE() AND CONSTRAINT_TYPE = 'FOREIGN KEY');
SET @have_fk = @want_fk - @missn_fk;
SET @pf_fk = IF(@missn_fk = 0,
    CONCAT('SELECT ''postflight foreign keys: ', @have_fk, '/', @want_fk, ' present'' AS postflight_fk'),
    CONCAT('SELECT `ABORT postflight: MISSING foreign keys: ', @miss_fk,
           ' -- ', @missn_fk, ' of ', @want_fk, ' absent. A step is recorded in',
           ' _migration_ledger but did not take effect.`'));
PREPARE _pf FROM @pf_fk; EXECUTE _pf; DEALLOCATE PREPARE _pf;

-- postflight-set: col
SET @want_col = 1;
SELECT COUNT(*), IFNULL(SUBSTRING(GROUP_CONCAT(n ORDER BY n SEPARATOR ', '), 1, 60), '')
  INTO @missn_col, @miss_col
  FROM (SELECT 'quotationdetails.ItemOriginID' AS n) _w
 WHERE n NOT IN (SELECT CONCAT(TABLE_NAME, '.', COLUMN_NAME) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE());
SET @have_col = @want_col - @missn_col;
SET @pf_col = IF(@missn_col = 0,
    CONCAT('SELECT ''postflight new columns: ', @have_col, '/', @want_col, ' present'' AS postflight_col'),
    CONCAT('SELECT `ABORT postflight: MISSING new columns: ', @miss_col,
           ' -- ', @missn_col, ' of ', @want_col, ' absent. A step is recorded in',
           ' _migration_ledger but did not take effect.`'));
PREPARE _pf FROM @pf_col; EXECUTE _pf; DEALLOCATE PREPARE _pf;

-- postflight-set: idx
SET @want_idx = 38;
SELECT COUNT(*), IFNULL(SUBSTRING(GROUP_CONCAT(n ORDER BY n SEPARATOR ', '), 1, 60), '')
  INTO @missn_idx, @miss_idx
  FROM (SELECT 'quotation.quotation_CompanyCPID' AS n UNION ALL SELECT 'quotation.quotation_CompanyID' UNION ALL SELECT 'quotation.quotation_DeliveryAddressID' UNION ALL SELECT 'quotation.quotation_DivisionID' UNION ALL SELECT 'quotation.quotation_InvoiceAddressID' UNION ALL SELECT 'quotation.quotation_QuotationDeliveryTimeID' UNION ALL SELECT 'quotation.quotation_QuotationFeedbackStatusID' UNION ALL SELECT 'quotation.quotation_QuotationPaymentTermID' UNION ALL SELECT 'quotation.quotation_QuotationPriceDescID' UNION ALL SELECT 'quotation.quotation_QuotationSalesTermID' UNION ALL SELECT 'quotation.quotation_QuotationStatusID' UNION ALL SELECT 'quotation.quotation_QuotationStockAvailibilityID' UNION ALL SELECT 'quotation.quotation_QuotationSubjectID' UNION ALL SELECT 'quotation.quotation_QuotationTitleID' UNION ALL SELECT 'quotation.quotation_QuotationTypeID' UNION ALL SELECT 'quotation.quotation_QuotationValidityID' UNION ALL SELECT 'quotation.quotation_ReferenceQuotationID' UNION ALL SELECT 'quotation.quotation_UserIDInput' UNION ALL SELECT 'quotation.quotation_UserIDSales' UNION ALL SELECT 'quotationassignment.qta_fk_QuotationID' UNION ALL SELECT 'quotationassignment.qta_fk_QuotationStatusID' UNION ALL SELECT 'quotationassignment.qta_fk_UserID' UNION ALL SELECT 'quotationdetails.quotationdetails_ApplicationID' UNION ALL SELECT 'quotationdetails.quotationdetails_BarangID' UNION ALL SELECT 'quotationdetails.quotationdetails_ItemOriginID' UNION ALL SELECT 'quotationdetails.quotationdetails_PackingID' UNION ALL SELECT 'quotationdetails.quotationdetails_ProjectID' UNION ALL SELECT 'quotationdetails.quotationdetails_ProjectIDDetails' UNION ALL SELECT 'quotationdetails.quotationdetails_QuotationDetailStatusID' UNION ALL SELECT 'quotationdetails.quotationdetails_QuotationID' UNION ALL SELECT 'quotationdetails.quotationdetails_QuotationPackID' UNION ALL SELECT 'quotationdetails.quotationdetails_SatuanID' UNION ALL SELECT 'quotationdetails.quotationdetails_SatuanOrderQty' UNION ALL SELECT 'quotationdetailsassignment.qda_fk_QuotationDetailID' UNION ALL SELECT 'quotationdetailsassignment.qda_fk_QuotationDetailStatusID' UNION ALL SELECT 'quotationdetailsassignment.qda_fk_QuotationID' UNION ALL SELECT 'quotationdetailsassignment.qda_fk_UserID' UNION ALL SELECT 'quotationlink.ql_fk_QuotationID') _w
 WHERE n NOT IN (SELECT CONCAT(TABLE_NAME, '.', INDEX_NAME) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE());
SET @have_idx = @want_idx - @missn_idx;
SET @pf_idx = IF(@missn_idx = 0,
    CONCAT('SELECT ''postflight indexes: ', @have_idx, '/', @want_idx, ' present'' AS postflight_idx'),
    CONCAT('SELECT `ABORT postflight: MISSING indexes: ', @miss_idx,
           ' -- ', @missn_idx, ' of ', @want_idx, ' absent. A step is recorded in',
           ' _migration_ledger but did not take effect.`'));
PREPARE _pf FROM @pf_idx; EXECUTE _pf; DEALLOCATE PREPARE _pf;

-- postflight-set: typ
SET @want_typ = 38;
SELECT COUNT(*), IFNULL(SUBSTRING(GROUP_CONCAT(n ORDER BY n SEPARATOR ', '), 1, 60), '')
  INTO @missn_typ, @miss_typ
  FROM (SELECT 'quotation.CompanyCPID.int' AS n UNION ALL SELECT 'quotation.CompanyID.int' UNION ALL SELECT 'quotation.DeliveryAddressID.int' UNION ALL SELECT 'quotation.DivisionID.int' UNION ALL SELECT 'quotation.IndustryID.varchar' UNION ALL SELECT 'quotation.InvoiceAddressID.int' UNION ALL SELECT 'quotation.QuotationDeliveryTimeID.int' UNION ALL SELECT 'quotation.QuotationFeedbackStatusID.int' UNION ALL SELECT 'quotation.QuotationPaymentTermID.int' UNION ALL SELECT 'quotation.QuotationPriceDescID.int' UNION ALL SELECT 'quotation.QuotationSalesTermID.int' UNION ALL SELECT 'quotation.QuotationStatusID.int' UNION ALL SELECT 'quotation.QuotationStockAvailibilityID.int' UNION ALL SELECT 'quotation.QuotationSubjectID.int' UNION ALL SELECT 'quotation.QuotationTitleID.int' UNION ALL SELECT 'quotation.QuotationTypeID.int' UNION ALL SELECT 'quotation.QuotationValidityID.int' UNION ALL SELECT 'quotation.ReferenceQuotationID.int' UNION ALL SELECT 'quotation.UserIDInput.int' UNION ALL SELECT 'quotation.UserIDSales.int' UNION ALL SELECT 'quotationassignment.QuotationID.int' UNION ALL SELECT 'quotationassignment.QuotationStatusID.int' UNION ALL SELECT 'quotationassignment.UserID.int' UNION ALL SELECT 'quotationdetails.ApplicationID.int' UNION ALL SELECT 'quotationdetails.BarangID.int' UNION ALL SELECT 'quotationdetails.PackingID.int' UNION ALL SELECT 'quotationdetails.ProjectID.int' UNION ALL SELECT 'quotationdetails.ProjectIDDetails.int' UNION ALL SELECT 'quotationdetails.QuotationDetailStatusID.int' UNION ALL SELECT 'quotationdetails.QuotationID.int' UNION ALL SELECT 'quotationdetails.QuotationPackID.int' UNION ALL SELECT 'quotationdetails.SatuanID.int' UNION ALL SELECT 'quotationdetails.SatuanOrderQty.int' UNION ALL SELECT 'quotationdetailsassignment.QuotationDetailID.int' UNION ALL SELECT 'quotationdetailsassignment.QuotationDetailStatusID.int' UNION ALL SELECT 'quotationdetailsassignment.QuotationID.int' UNION ALL SELECT 'quotationdetailsassignment.UserID.int' UNION ALL SELECT 'quotationlink.QuotationID.int') _w
 WHERE n NOT IN (SELECT CONCAT(TABLE_NAME, '.', COLUMN_NAME, '.', DATA_TYPE) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE());
SET @have_typ = @want_typ - @missn_typ;
SET @pf_typ = IF(@missn_typ = 0,
    CONCAT('SELECT ''postflight retyped columns: ', @have_typ, '/', @want_typ, ' present'' AS postflight_typ'),
    CONCAT('SELECT `ABORT postflight: MISSING retyped columns: ', @miss_typ,
           ' -- ', @missn_typ, ' of ', @want_typ, ' absent. A step is recorded in',
           ' _migration_ledger but did not take effect.`'));
PREPARE _pf FROM @pf_typ; EXECUTE _pf; DEALLOCATE PREPARE _pf;

-- --------------------------------------------------------------- restore
-- IFNULL on all four: if phpMyAdmin resumed this import on a fresh connection the
-- @OLD_ variables are gone, and a bare SET from NULL is ERROR 1231.
--
-- The CAST is on the two timeouts and NOWHERE ELSE, on purpose. An INTEGER system
-- variable rejects IFNULL()'s result type outright (ERROR 1232, every time, not only
-- when NULL). FOREIGN_KEY_CHECKS is a boolean and SQL_MODE is a set; both accept a
-- string result, so they need no CAST. Do not "fix" the asymmetry, and do not copy
-- line 3's shape onto a new integer variable.
SET SESSION lock_wait_timeout = CAST(IFNULL(@OLD_LOCK_WAIT, 86400) AS UNSIGNED);
SET SESSION innodb_lock_wait_timeout = CAST(IFNULL(@OLD_INNODB_LOCK_WAIT, 50) AS UNSIGNED);
